We are in official beta.

Sources

Where a workflow's data comes from — SQL and Dataverse rows, SharePoint documents, Azure blobs, S3 objects, SFTP files, and Service Bus messages, all through a saved connection

31 endpoints in this category. All require X-API-Key header.


Dataverse: Query Rows

POST /api/DataverseQueryRows 1 token

Reads rows out of a Dataverse (Dynamics 365 / Power Platform) table via a registry connection of type 'dataverse' owned by the calling client. SELECT-ONLY BY CONSTRUCTION: this endpoint issues nothing but GET requests, so there is no code path through it that could write. The connection holds the org URL and credential — neither ever appears in the workflow definition.

Rows come back as plain JSON with the '@odata.etag' annotation dropped at every depth; other annotations and '_*_value' lookup columns pass through untouched. 'count' is the rows on THIS page and 'totalCount' the rows matching the filter, so a short page never reads as the whole answer; 'hasMore' plus 'cursor' continue the read, and a page that ends without a cursor is the end of the result set. Dataverse stops counting at 5000, which is reported as totalCountExact false rather than passed off as a total. A body key this endpoint does not accept is a 400 naming it — an ignored scoping parameter would answer a different question than the one you asked. Reading is not a side effect: a retry costs a second read. Not available in the anonymous playground or interactive builder Run (connection steps execute server-side via webhooks/form submissions).
Parameters
NameTypeRequiredDescription
connectionId string required Registry connection id (conn_…) of type 'dataverse', owned by the calling client (manage/connections)
entitySet string required Dataverse Web API entity set name — plural, lowercase (e.g. 'contacts', 'cr123_orders'). Use DataverseListEntitySets to see what exists
top number optional Page size: how many rows one call returns, 1 to 500. A value above 500 is refused rather than quietly reduced; a bigger result is read by following 'cursor' (default: 5)
select string optional Comma-separated attribute logical names to return (e.g. 'firstname,lastname,emailaddress1'). Omit for every attribute the table exposes
filter array optional Scope the read. An array of conditions, all ANDed: [{"attribute": "_parentcustomerid_value", "operator": "eq", "value": "<guid>"}]. Operators: eq, ne, gt, ge, lt, le, contains, startswith, endswith, null, notnull, in, notin (in and notin each take an array of up to 20 values). Add "valueType" (string, number, boolean, guid, datetime) when the value's type is not obvious from JSON — a date-only value needs it. For OR, or to nest, pass {"logic": "or", "conditions": [...]} instead of an array. A raw OData $filter string is deliberately NOT accepted: the clause is compiled by the endpoint so no caller-supplied text reaches the query
orderBy array optional Sort order, up to 5 attributes: [{"attribute": "createdon", "direction": "desc"}]. Direction defaults to 'asc'. Worth setting whenever you page — without an order, which rows land on which page is undefined
cursor string optional Continuation token. Pass back the 'cursor' from a previous response, unchanged, to read the next page. Only valid for the same query. It works the same way for a native FetchXML read, which pages with Dataverse's own paging cookie rather than a skip token — send the SAME 'fetchXml' document again with the cursor beside it, and the page number and paging cookie are written into the document by this endpoint
count boolean optional Ask Dataverse for the total number of MATCHING rows alongside this page (returned as totalCount). On by default; set false to skip the count (default: true)
expand array optional Related data to pull alongside each row, one level deep: [{"navigation": "parentcustomerid_account", "select": "name,telephone1", "top": 3}]. A lookup arrives as a nested object on the row, a child collection as a nested array; 'top' applies to child collections only (a lookup returns at most one row and refuses it). A child collection may also carry 'filter', the same structured shape the top-level filter takes, compiled into $expand=nav($filter=...) — OData has no filter inside a single-valued expand, so one on a lookup is refused rather than ignored. Every navigation name must be one DataverseListRelationships reports for this table, or the call is refused
fetchXml string optional A native FetchXML document, run through the Web API's own ?fetchXml=. For the queries the structured filter cannot express — aggregates, relative dates (last-x-days), user-scoped operators, EXISTS joins, deeper and/or nesting. MUTUALLY EXCLUSIVE with select, filter, orderBy, expand and count: a FetchXML document is the whole query and the Web API composes nothing beside it, so sending both is a 400 naming which. Validated first: well-formed XML with no DOCTYPE, exactly one <fetch> root and one <entity>, at most 20000 characters and 500 elements, no paging-cookie and no 'page' attribute, and a top/count inside the same 1-500 ceiling as 'top' (refused, never reduced). It DOES page: send the same document back with the 'cursor' from the previous response and the page number and paging cookie are written into the document by this endpoint, which is why a pasted one is refused. 'entitySet' is still required — it is the path the document is run against
Request Example
JSON
{"connectionId": "conn_0123456789abcdef0123456789abcdef", "entitySet": "contacts", "top": 100, "select": "fullname,jobtitle", "filter": [{"attribute": "_parentcustomerid_value", "operator": "eq", "value": "07c59639-d6a8-f111-aaac-6045bd015278"}], "orderBy": [{"attribute": "fullname", "direction": "asc"}]}
Response Example
JSON
{"success": true, "rows": [{"fullname": "Alina Petrova", "jobtitle": "Benefits Lead"}], "count": 1, "pageSize": 100, "hasMore": false, "totalCount": 9, "totalCountExact": true, "filterConditions": 1}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/DataverseQueryRows" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"connectionId": "conn_0123456789abcdef0123456789abcdef", "entitySet": "contacts", "top": 100, "select": "fullname,jobtitle", "filter": [{"attribute": "_parentcustomerid_value", "operator": "eq", "value": "07c59639-d6a8-f111-aaac-6045bd015278"}], "orderBy": [{"attribute": "fullname", "direction": "asc"}]}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""connectionId"": ""conn_0123456789abcdef0123456789abcdef"", ""entitySet"": ""contacts"", ""top"": 100, ""select"": ""fullname,jobtitle"", ""filter"": [{""attribute"": ""_parentcustomerid_value"", ""operator"": ""eq"", ""value"": ""07c59639-d6a8-f111-aaac-6045bd015278""}], ""orderBy"": [{""attribute"": ""fullname"", ""direction"": ""asc""}]}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/DataverseQueryRows", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/DataverseQueryRows"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"connectionId": "conn_0123456789abcdef0123456789abcdef", "entitySet": "contacts", "top": 100, "select": "fullname,jobtitle", "filter": [{"attribute": "_parentcustomerid_value", "operator": "eq", "value": "07c59639-d6a8-f111-aaac-6045bd015278"}], "orderBy": [{"attribute": "fullname", "direction": "asc"}]}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/DataverseQueryRows
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "connectionId": "conn_0123456789abcdef0123456789abcdef",
│      "entitySet": "contacts",
│      "top": 100,
│      "select": "fullname,jobtitle",
│      "filter": [
│        {
│          "attribute": "_parentcustomerid_value",
│          "operator": "eq",
│          "value": "07c59639-d6a8-f111-aaac-6045bd015278"
│        }
│      ],
│      "orderBy": [
│        {
│          "attribute": "fullname",
│          "direction": "asc"
│        }
│      ]
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/DataverseQueryRows"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

Dataverse: List Entity Sets

POST /api/DataverseListEntitySets 1 token

Lists the tables a Dataverse connection can see, as the entity set names DataverseQueryRows and DataverseCreateRows take. This is the discovery half of the source: 'process the customer's orders table' is unwritable if the only question you can ask is 'give me this exact table'.

Each entry carries BOTH names, because they are not derivable from each other: 'name' is the entity set ('contacts') that every Web API URL and this connector's 'entitySet' take, and 'logicalName' is the singular ('contact') that FetchXML, plugin registrations and the SDK use. The default set name is a plural of the logical name, but a publisher can set it to anything and a few first-party tables do not follow the rule, so translating one into the other by pluralizing names a table the org may not have. logicalName is omitted for a definition that reports none, rather than guessed. Private entities are excluded. displayName falls back to the entity set name when the table carries no user-facing label. The list is cached per connection for about five minutes, and ownership plus liveness are re-verified on every call before the cache is consulted — a revoked connection never answers from cache. Reading is not a side effect.
Parameters
NameTypeRequiredDescription
connectionId string required Registry connection id (conn_…) of type 'dataverse', owned by the calling client (manage/connections)
Request Example
JSON
{"connectionId": "conn_0123456789abcdef0123456789abcdef"}
Response Example
JSON
{"success": true, "entitySets": [{"name": "contacts", "logicalName": "contact", "displayName": "Contact"}, {"name": "cr123_orders", "logicalName": "cr123_order", "displayName": "Order"}]}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/DataverseListEntitySets" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"connectionId": "conn_0123456789abcdef0123456789abcdef"}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""connectionId"": ""conn_0123456789abcdef0123456789abcdef""}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/DataverseListEntitySets", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/DataverseListEntitySets"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"connectionId": "conn_0123456789abcdef0123456789abcdef"}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/DataverseListEntitySets
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "connectionId": "conn_0123456789abcdef0123456789abcdef"
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/DataverseListEntitySets"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

Dataverse: List Attributes

POST /api/DataverseListAttributes 1 token

Lists the columns of ONE Dataverse table. The column half of discovery: DataverseListEntitySets answers 'which tables', this answers 'which fields', which is what a field-mapping screen needs before it can propose anything. Two lists, chosen with 'mode': 'write' (the default) gives the columns DataverseCreateRows can set, and 'read' gives the columns a query can select — including createdon, statecode and the lookup ids, which are not writable and so are absent from the default list.

An entity set this org does not have is a 404 naming it, never an empty attribute list — 'no columns' and 'no such table' are different answers. required is meaningful on the write list only, and is true only for Dataverse's ApplicationRequired and SystemRequired levels; Recommended is optional as far as a create call is concerned. On the write list a lookup reports type 'Lookup' under its own name and is written with the '@odata.bind' form, which a plain field mapping does not produce (see DataverseCreateRows lookupBinds). On the read list the same lookup is reported as '_<name>_value', which is what a select accepts, with the raw name in 'attributeLogicalName'; derived companions such as statuscodename are left out because naming one in a select is an error. The two lists are cached separately, per connection, entity set and mode, for about five minutes, with ownership and liveness re-verified on every call before the cache is consulted. Reading is not a side effect.
Parameters
NameTypeRequiredDescription
connectionId string required Registry connection id (conn_…) of type 'dataverse', owned by the calling client (manage/connections)
entitySet string required Dataverse Web API entity set name — plural, lowercase (e.g. 'contacts', 'cr123_orders'). Use DataverseListEntitySets to see what exists
mode string optional Which list: 'write' (default) for the columns you can set when creating a row, or 'read' for the columns you can name in a query's select. Anything else is refused rather than defaulted
Request Example
JSON
{"connectionId": "conn_0123456789abcdef0123456789abcdef", "entitySet": "contacts"}
Response Example
JSON
{"success": true, "entitySet": "contacts", "logicalName": "contact", "mode": "write", "attributes": [{"logicalName": "emailaddress1", "displayName": "Email", "type": "String", "required": false}, {"logicalName": "lastname", "displayName": "Last Name", "type": "String", "required": true}]}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/DataverseListAttributes" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"connectionId": "conn_0123456789abcdef0123456789abcdef", "entitySet": "contacts"}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""connectionId"": ""conn_0123456789abcdef0123456789abcdef"", ""entitySet"": ""contacts""}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/DataverseListAttributes", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/DataverseListAttributes"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"connectionId": "conn_0123456789abcdef0123456789abcdef", "entitySet": "contacts"}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/DataverseListAttributes
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "connectionId": "conn_0123456789abcdef0123456789abcdef",
│      "entitySet": "contacts"
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/DataverseListAttributes"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

Dataverse: List Relationships

POST /api/DataverseListRelationships 1 token

Lists the relationships of ONE Dataverse table: its lookups (the columns pointing at a parent row) and its child collections (the rows that point back at it). The third half of discovery — entity sets answer 'which tables', attributes answer 'which columns', and this answers 'what is this table connected to', which is what a query has to know before it can pull a contact together with the account it belongs to. The navigation property names it returns are exactly what DataverseQueryRows takes in its 'expand'.

An entity set this org does not have is a 404 naming it, never an empty list. A relationship whose target is private, or has no entity set of its own, is left out rather than offered — nothing here may name something DataverseQueryRows could not then read. referencedPrimaryId / childPrimaryId come from the entity's own metadata (never guessed as '<name>id'), and are what a write-back needs to set the lookup with @odata.bind. Cached per connection and entity set for about five minutes, with ownership and liveness re-verified on every call before the cache is consulted. Reading is not a side effect.
Parameters
NameTypeRequiredDescription
connectionId string required Registry connection id (conn_…) of type 'dataverse', owned by the calling client (manage/connections)
entitySet string required Dataverse Web API entity set name — plural, lowercase (e.g. 'contacts', 'cr123_orders'). Use DataverseListEntitySets to see what exists
Request Example
JSON
{"connectionId": "conn_0123456789abcdef0123456789abcdef", "entitySet": "contacts"}
Response Example
JSON
{"success": true, "entitySet": "contacts", "logicalName": "contact", "lookups": [{"navigation": "parentcustomerid_account", "referencingAttribute": "parentcustomerid", "referencedEntity": "account", "referencedEntitySet": "accounts", "referencedPrimaryId": "accountid", "displayName": "Account"}], "children": [{"navigation": "Contact_Tasks", "referencingAttribute": "regardingobjectid", "childEntity": "task", "childEntitySet": "tasks", "childPrimaryId": "activityid", "displayName": "Task"}]}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/DataverseListRelationships" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"connectionId": "conn_0123456789abcdef0123456789abcdef", "entitySet": "contacts"}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""connectionId"": ""conn_0123456789abcdef0123456789abcdef"", ""entitySet"": ""contacts""}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/DataverseListRelationships", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/DataverseListRelationships"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"connectionId": "conn_0123456789abcdef0123456789abcdef", "entitySet": "contacts"}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/DataverseListRelationships
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "connectionId": "conn_0123456789abcdef0123456789abcdef",
│      "entitySet": "contacts"
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/DataverseListRelationships"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

Dataverse: Read File Column

POST /api/DataverseReadFile 1 token

Reads the file held in a Dataverse File (or Image) column and returns it as base64, through a registry connection of type 'dataverse' owned by the calling client. This is the read end of the document drop point: a flow writes a bundle into a child record's file column, a workflow picks it up here, works on it, and writes it back. Dataverse serves files in chunks, and this endpoint reassembles them — the caller gets one complete document or an error, never a partial one.

Up to 33554432 bytes (32 MB) per call — a larger file is refused with 413 and its size, and nothing is transferred, because the size is read from a one-byte ranged request before the download starts. Dataverse stores no media type for a file column, so contentType is DERIVED and contentTypeSource says from what: 'signature' when the bytes identify themselves, 'extension' when only the file name does, 'default' when neither. The file name is what Dataverse does keep, and it round-trips. An empty column is 404 FILE_COLUMN_EMPTY, a missing record is 404 DATAVERSE_RECORD_NOT_FOUND, and a column that is not a file column is 400 NOT_A_FILE_COLUMN — three different answers, not one. Reading is not a side effect: a retry costs a second read. Not available in the anonymous playground or interactive builder Run (connection steps execute server-side via webhooks/form submissions).
Parameters
NameTypeRequiredDescription
connectionId string required Registry connection id (conn_…) of type 'dataverse', owned by the calling client (manage/connections)
entitySet string required Dataverse Web API entity set name — plural, lowercase (e.g. 'dbf_documentdrops', 'contacts')
recordId string required The record's GUID, plain 8-4-4-4-12 form with no braces
column string required The File column's attribute logical name (e.g. 'dbf_file'). DataverseListAttributes reports the column types
Request Example
JSON
{"connectionId": "conn_0123456789abcdef0123456789abcdef", "entitySet": "dbf_documentdrops", "recordId": "be83416a-45a9-f111-aaac-70a8a5b10a05", "column": "dbf_file"}
Response Example
JSON
{"success": true, "entitySet": "dbf_documentdrops", "recordId": "be83416a-45a9-f111-aaac-70a8a5b10a05", "column": "dbf_file", "fileName": "proposal-bundle.zip", "contentType": "application/zip", "contentTypeSource": "signature", "sizeBytes": 236544, "contentBase64": "UEsDBBQ…", "chunks": 1, "timing": {"total": 812}}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/DataverseReadFile" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"connectionId": "conn_0123456789abcdef0123456789abcdef", "entitySet": "dbf_documentdrops", "recordId": "be83416a-45a9-f111-aaac-70a8a5b10a05", "column": "dbf_file"}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""connectionId"": ""conn_0123456789abcdef0123456789abcdef"", ""entitySet"": ""dbf_documentdrops"", ""recordId"": ""be83416a-45a9-f111-aaac-70a8a5b10a05"", ""column"": ""dbf_file""}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/DataverseReadFile", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/DataverseReadFile"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"connectionId": "conn_0123456789abcdef0123456789abcdef", "entitySet": "dbf_documentdrops", "recordId": "be83416a-45a9-f111-aaac-70a8a5b10a05", "column": "dbf_file"}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/DataverseReadFile
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "connectionId": "conn_0123456789abcdef0123456789abcdef",
│      "entitySet": "dbf_documentdrops",
│      "recordId": "be83416a-45a9-f111-aaac-70a8a5b10a05",
│      "column": "dbf_file"
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/DataverseReadFile"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

SQL: Query Rows

POST /api/SqlQueryRows 1 token

Reads rows out of a SQL table via a registry connection of type 'sqlserver', 'postgresql', or 'mysql' owned by the calling client. SELECT-ONLY BY CONSTRUCTION: the caller NEVER supplies SQL text — the table and column names are validated against the identifier alphabet, verified to exist in INFORMATION_SCHEMA, and quoted per dialect, the WHERE clause is COMPILED by us from a structured filter with every value bound as a driver parameter, and the row limit is injected by us from a validated integer. The dialect comes from the connection; the request is identical for all three.

A table the login cannot see is a 404 naming the table, never a driver error. A body key this endpoint does not honor is a 400 UNKNOWN_PARAMETER naming it — an ignored scoping parameter would answer a different question than the one you asked. Every read says whether it is the whole answer: hasMore/truncated are the same fact, and the page is one row longer than the page size internally so truncation is measured rather than guessed. The target database must be reachable from Azure. The database password appears in no response, log line, or error message — driver failures cross the boundary code-first, truncated, and scrubbed. Reading is not a side effect: a retry costs a second read. Not available in the anonymous playground or interactive builder Run (connection steps execute server-side via webhooks/form submissions).
Parameters
NameTypeRequiredDescription
connectionId string required Registry connection id (conn_…) of type 'sqlserver', 'postgresql', or 'mysql', owned by the calling client (manage/connections)
table string required Source table name, optionally schema-qualified (e.g. 'invoices', 'dbo.Invoices', 'public.orders'). Names match exactly as stored — use SqlListTables to see what exists
top number optional Page size: how many rows one call returns, 1 to 500. A value above 500 is refused rather than quietly reduced; a bigger result is read by following 'cursor' (default: 5)
columns string optional Comma-separated column names to return (e.g. 'id,customer,total'). Every name is checked against the table's real columns first. Omit for every column
filter array optional Scope the read. An array of conditions, all ANDed: [{"column": "status", "operator": "eq", "value": "open"}]. Operators: eq, ne, gt, ge, lt, le, contains, startswith, endswith, null, notnull, in, notin (in and notin each take an array of up to 20 values). Add "valueType" (string, number, boolean, guid, datetime) when the value's type is not obvious from JSON. For OR, or to nest, pass {"logic": "or", "conditions": [...]} instead of an array. A raw WHERE string is deliberately NOT accepted: the clause is compiled by the endpoint and every value travels as a driver parameter, so no caller text reaches the statement
orderBy array optional Sort order, up to 5 columns: [{"column": "created_at", "direction": "desc"}]. Direction defaults to 'asc'. Required before you can page — without a defined order, which rows land on which page is undefined and a cursor can repeat or skip a row
cursor string optional Continuation token. Pass back the 'cursor' from a previous response, unchanged, to read the next page. It is bound to the query that issued it: the same table, columns, filter and orderBy
count boolean optional Ask the database for the total number of MATCHING rows alongside this page (returned as totalCount). Off by default — it is a second COUNT(*) statement (default: false)
Request Example
JSON
{"connectionId": "conn_0123456789abcdef0123456789abcdef", "table": "dbo.Invoices", "top": 5, "columns": "id,customer,total", "filter": [{"column": "customer", "operator": "eq", "value": "Acme Corp"}], "orderBy": [{"column": "id", "direction": "asc"}]}
Response Example
JSON
{"success": true, "rows": [{"id": 1, "customer": "Acme Corp", "total": 582.62}], "count": 1, "pageSize": 5, "hasMore": false, "truncated": false, "filterConditions": 1}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/SqlQueryRows" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"connectionId": "conn_0123456789abcdef0123456789abcdef", "table": "dbo.Invoices", "top": 5, "columns": "id,customer,total", "filter": [{"column": "customer", "operator": "eq", "value": "Acme Corp"}], "orderBy": [{"column": "id", "direction": "asc"}]}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""connectionId"": ""conn_0123456789abcdef0123456789abcdef"", ""table"": ""dbo.Invoices"", ""top"": 5, ""columns"": ""id,customer,total"", ""filter"": [{""column"": ""customer"", ""operator"": ""eq"", ""value"": ""Acme Corp""}], ""orderBy"": [{""column"": ""id"", ""direction"": ""asc""}]}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/SqlQueryRows", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/SqlQueryRows"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"connectionId": "conn_0123456789abcdef0123456789abcdef", "table": "dbo.Invoices", "top": 5, "columns": "id,customer,total", "filter": [{"column": "customer", "operator": "eq", "value": "Acme Corp"}], "orderBy": [{"column": "id", "direction": "asc"}]}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/SqlQueryRows
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "connectionId": "conn_0123456789abcdef0123456789abcdef",
│      "table": "dbo.Invoices",
│      "top": 5,
│      "columns": "id,customer,total",
│      "filter": [
│        {
│          "column": "customer",
│          "operator": "eq",
│          "value": "Acme Corp"
│        }
│      ],
│      "orderBy": [
│        {
│          "column": "id",
│          "direction": "asc"
│        }
│      ]
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/SqlQueryRows"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

SQL: List Tables

POST /api/SqlListTables 1 token

Lists the tables a SQL connection can see, with each table's columns and their data types, read from INFORMATION_SCHEMA in all three dialects, and each table's foreign keys (the columns that point at another table, paired with the columns they reference). The discovery half of the SQL source, and what the data browser builds its table picker and its related-tables list from.

Sorted by schema then name and CAPPED AT 500 TABLES, with truncated:true when the database holds more — an unbounded read of a customer's catalog is a cost liability, not a feature. Only what the connection's login can see is listed. Each table lists the foreign keys that LEAVE it; a key pointing at a table is found on the table it comes from. foreignKeysStatus says whether that list is whole: 'complete', 'truncated' (past 5,000 key columns; a key is never reported with only some of its columns) or 'unavailable' (the key read failed, and the tables are still returned with empty foreignKeys lists that do not mean 'no keys'). Reading is not a side effect.
Parameters
NameTypeRequiredDescription
connectionId string required Registry connection id (conn_…) of type 'sqlserver', 'postgresql', or 'mysql', owned by the calling client (manage/connections)
Request Example
JSON
{"connectionId": "conn_0123456789abcdef0123456789abcdef"}
Response Example
JSON
{"success": true, "tables": [{"schema": "dbo", "name": "Invoices", "columns": [{"name": "id", "dataType": "int", "nullable": false}, {"name": "customer", "dataType": "nvarchar", "nullable": true}, {"name": "customer_id", "dataType": "int", "nullable": true}], "foreignKeys": [{"name": "FK_Invoices_Customers", "columns": ["customer_id"], "referencedSchema": "dbo", "referencedTable": "Customers", "referencedColumns": ["id"]}]}], "truncated": false, "foreignKeysStatus": "complete"}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/SqlListTables" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"connectionId": "conn_0123456789abcdef0123456789abcdef"}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""connectionId"": ""conn_0123456789abcdef0123456789abcdef""}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/SqlListTables", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/SqlListTables"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"connectionId": "conn_0123456789abcdef0123456789abcdef"}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/SqlListTables
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "connectionId": "conn_0123456789abcdef0123456789abcdef"
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/SqlListTables"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

Blob: Read File

POST /api/BlobReadFile 1 token

Reads a file out of an Azure Blob Storage container via a registry connection of type 'blob' owned by the calling client. The read half of #1228's write: until it landed a pipeline could deliver a document into a customer's container and could never pick one up. The connection holds the account URL and the SAS token or storage connection string — neither ever appears in the workflow definition.

EXACTLY ONE FIELD CARRIES THE PAYLOAD, and 'encoding' names which: a textual blob comes back in 'content', anything else in 'contentBase64', never both — returning both would double a 32 MB download on the wire and hand a caller base64 that looks like content. Override the sniff with 'encoding' when the customer's own metadata is wrong: a PDF mislabeled text/plain would otherwise be decoded as UTF-8 and come back corrupt. The 32 MB cap is enforced on the blob's reported length BEFORE a byte transfers, and the download is pinned to the etag we measured, so a blob replaced mid-read fails cleanly instead of returning half of one document and half of another. The returned URL never carries the SAS — the query string is stripped before it leaves. Reading is not a side effect: a retry costs a second read. Not available in the anonymous playground or interactive builder Run.
Parameters
NameTypeRequiredDescription
connectionId string required Registry connection id (conn_…) of type 'blob', owned by the calling client (manage/connections)
path string required Blob name, optionally with folder segments (e.g. 'invoices/2026/inv-1.pdf'). Max 1024 characters; no leading slash, no '..', no control characters
container string optional Overrides the connection's default container. Azure naming rules: 3-63 lowercase alphanumerics and single dashes
encoding string optional Which payload field to answer with. 'auto' decides from the blob's own content type; 'text' forces UTF-8 text; 'base64' forces bytes (default: auto) (one of: auto, text, base64)
Request Example
JSON
{"connectionId": "conn_0123456789abcdef0123456789abcdef", "path": "invoices/2026/inv-1.pdf"}
Response Example
JSON
{"success": true, "container": "invoices", "path": "invoices/2026/inv-1.pdf", "url": "https://acmedocs.blob.core.windows.net/invoices/2026/inv-1.pdf", "contentType": "application/pdf", "sizeBytes": 20481, "etag": "\"0x8DC…\"", "lastModified": "2026-08-23T10:15:00.000Z", "encoding": "base64", "contentBase64": "JVBERi0xLjcK...", "timing": {"total": 96}}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/BlobReadFile" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"connectionId": "conn_0123456789abcdef0123456789abcdef", "path": "invoices/2026/inv-1.pdf"}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""connectionId"": ""conn_0123456789abcdef0123456789abcdef"", ""path"": ""invoices/2026/inv-1.pdf""}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/BlobReadFile", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/BlobReadFile"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"connectionId": "conn_0123456789abcdef0123456789abcdef", "path": "invoices/2026/inv-1.pdf"}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/BlobReadFile
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "connectionId": "conn_0123456789abcdef0123456789abcdef",
│      "path": "invoices/2026/inv-1.pdf"
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/BlobReadFile"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

Blob: List Files

POST /api/BlobListFiles 1 token

Lists the blobs under a prefix in an Azure Blob Storage container via a registry connection of type 'blob'. A source without discovery is half a source: 'process every PDF that landed today' is unwritable if the only question you can ask is 'give me this exact path'.

Sorted by the service (lexicographic by name) and CAPPED AT 500 BLOBS, with truncated:true when the cap bites — the same convention SqlListTables and SftpListFiles use, so a caller learns one rule for every discovery endpoint. There is no continuation token: truncated:true is the honest signal that a narrower prefix is needed, and paging is a separate decision rather than something to infer from a missing field. Reading is not a side effect.
Parameters
NameTypeRequiredDescription
connectionId string required Registry connection id (conn_…) of type 'blob', owned by the calling client (manage/connections)
container string optional Overrides the connection's default container. Azure naming rules: 3-63 lowercase alphanumerics and single dashes
prefix string optional The start of the blob names to list (e.g. 'invoices/2026/'). This is a name prefix, not a folder — it need not end at a '/'. Omit to list the whole container
Request Example
JSON
{"connectionId": "conn_0123456789abcdef0123456789abcdef", "container": "invoices", "prefix": "2026/"}
Response Example
JSON
{"success": true, "container": "invoices", "prefix": "2026/", "files": [{"name": "2026/inv-1.pdf", "sizeBytes": 20481, "contentType": "application/pdf", "lastModified": "2026-08-23T10:15:00.000Z", "etag": "\"0x8DC…\""}], "count": 1, "truncated": false, "timing": {"total": 118}}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/BlobListFiles" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"connectionId": "conn_0123456789abcdef0123456789abcdef", "container": "invoices", "prefix": "2026/"}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""connectionId"": ""conn_0123456789abcdef0123456789abcdef"", ""container"": ""invoices"", ""prefix"": ""2026/""}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/BlobListFiles", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/BlobListFiles"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"connectionId": "conn_0123456789abcdef0123456789abcdef", "container": "invoices", "prefix": "2026/"}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/BlobListFiles
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "connectionId": "conn_0123456789abcdef0123456789abcdef",
│      "container": "invoices",
│      "prefix": "2026/"
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/BlobListFiles"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

SFTP: Download File

POST /api/SftpDownloadFile 1 token

Downloads a file from an SFTP server via a registry connection of type 'sftp' owned by the calling client. The read half of #1227's upload. The connection holds the host and the password or private key — neither ever appears in the workflow definition.

PATH CONFINEMENT IS THE SAME RULE AS THE UPLOAD: reads are held to exactly the write's standard, because a source that could fetch /etc/shadow while the destination refuses to escape the drop folder would be a hole shaped like a feature. SFTP carries no content type, so the bytes ride on contentBase64 and there is no text twin to choose between. The remote file is statted first and refused over 32 MB before a byte moves, and the decoded buffer is re-checked afterwards because stat and read are two round trips and a file can grow between them. Host key: when the connection sets config.hostKeyFingerprint it is ENFORCED — a mismatch aborts BEFORE authentication, so a man in the middle never sees the credential. Reading is not a side effect.
Parameters
NameTypeRequiredDescription
connectionId string required Registry connection id (conn_…) of type 'sftp', owned by the calling client (manage/connections)
path string required Remote path relative to the connection's rootPath (e.g. 'inbox/inv-1.pdf'). Max 1024 characters; absolute paths, '..', backslashes, empty segments and control characters are refused
Request Example
JSON
{"connectionId": "conn_0123456789abcdef0123456789abcdef", "path": "inbox/inv-1.pdf"}
Response Example
JSON
{"success": true, "path": "/upload/inbox/inv-1.pdf", "sizeBytes": 20481, "modifiedTime": 1755945300000, "contentBase64": "JVBERi0xLjcK...", "host": "sftp.acme.example", "port": 22, "hostKeyFingerprint": "SHA256:9x…", "hostKeyPinned": true, "timing": {"total": 612}}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/SftpDownloadFile" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"connectionId": "conn_0123456789abcdef0123456789abcdef", "path": "inbox/inv-1.pdf"}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""connectionId"": ""conn_0123456789abcdef0123456789abcdef"", ""path"": ""inbox/inv-1.pdf""}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/SftpDownloadFile", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/SftpDownloadFile"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"connectionId": "conn_0123456789abcdef0123456789abcdef", "path": "inbox/inv-1.pdf"}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/SftpDownloadFile
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "connectionId": "conn_0123456789abcdef0123456789abcdef",
│      "path": "inbox/inv-1.pdf"
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/SftpDownloadFile"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

SFTP: List Files

POST /api/SftpListFiles 1 token

Lists a directory on an SFTP server via a registry connection of type 'sftp'. This is the endpoint that makes 'process everything in the inbound folder' writable.

Entries are sorted by name and CAPPED AT 500, with truncated:true when the cap bites — a customer whose inbound folder holds 40,000 files gets 500 and an honest flag rather than a timeout. 'type' is one of file, directory, symlink or other. A path that is not there is a 404 naming the path, never a raw ssh2 error. Reading is not a side effect.
Parameters
NameTypeRequiredDescription
connectionId string required Registry connection id (conn_…) of type 'sftp', owned by the calling client (manage/connections)
path string optional Remote directory relative to the connection's rootPath (e.g. 'inbound'). Absolute paths, '..', backslashes and control characters are refused (default: the connection's rootPath)
limit number optional How many entries to return. Clamped to 500 (default: 500)
filesOnly boolean optional Drop directories, symlinks and anything else from the listing (default: false)
Request Example
JSON
{"connectionId": "conn_0123456789abcdef0123456789abcdef", "path": "inbound", "filesOnly": true}
Response Example
JSON
{"success": true, "path": "/upload/inbound", "entries": [{"name": "inv-1.pdf", "type": "file", "sizeBytes": 20481, "modifiedTime": 1755945300000}], "count": 1, "truncated": false, "host": "sftp.acme.example", "port": 22, "hostKeyFingerprint": "SHA256:9x…", "hostKeyPinned": true, "timing": {"total": 488}}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/SftpListFiles" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"connectionId": "conn_0123456789abcdef0123456789abcdef", "path": "inbound", "filesOnly": true}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""connectionId"": ""conn_0123456789abcdef0123456789abcdef"", ""path"": ""inbound"", ""filesOnly"": true}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/SftpListFiles", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/SftpListFiles"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"connectionId": "conn_0123456789abcdef0123456789abcdef", "path": "inbound", "filesOnly": true}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/SftpListFiles
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "connectionId": "conn_0123456789abcdef0123456789abcdef",
│      "path": "inbound",
│      "filesOnly": true
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/SftpListFiles"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

SFTP: Poll Watcher

POST /api/SftpWatchPoll 1 token

Runs one SFTP watcher's check immediately: lists the watched folder, works out what is new or changed since the last poll, and starts the registered workflow for each file. The same thing the watcher's timer does on its schedule — this is the button for 'check now', and for seeing what a watcher would pick up before you leave it running.

BILLED PER FILE DELIVERED, not per poll — one token for each file handed to your workflow, with a floor of one token for a poll you asked for yourself. The watcher's own timer charges nothing when it finds nothing, however often it runs. The workflow bills its own steps separately; this is the delivery, not the processing. SFTP HAS NO CHANGE FEED, so the watcher remembers each file's name, size and modification time and treats anything new or altered as changed — which also catches a file re-copied with its original timestamp, where a plain 'modified since' comparison delivers nothing. THE FIRST POLL DELIVERS NOTHING: it records what is already in the folder, so pointing a watcher at an inbound directory that already holds ten thousand files does not reprocess them. A deleted file is dropped from that memory and never delivered — there is nothing left to fetch. At most 30 files move per poll; a burst drains over several polls, and 'more: true' says so. A folder holding more than 500 entries SUSPENDS the watcher with a reason instead of being half-watched. Each file arrives as its listing row plus the connectionId and the folder, so the workflow's first step can be SftpDownloadFile — the bytes are never pushed into the pipeline input. Uses a saved SFTP connection, so it is not available in the anonymous playground or the interactive builder Run.
Parameters
NameTypeRequiredDescription
watchId string required The watcher to poll (sfw_…), created through manage/sftp/{clientId}/watchers
dryRun boolean optional Report what WOULD be delivered without starting any workflow and without charging. The folder is not marked as seen either, so a real poll afterwards still finds the same files (default: false)
Request Example
JSON
{"watchId": "sfw_0123456789abcdef0123456789abcdef"}
Response Example
JSON
{"success": true, "watchId": "sfw_0123456789abcdef0123456789abcdef", "source": "sftp", "workflowSlug": "invoice-intake", "folderPath": "inbound", "dryRun": false, "baseline": false, "changed": 2, "delivered": 2, "more": false, "failures": [], "files": [{"name": "inv-9.pdf", "path": "inbound/inv-9.pdf", "sizeBytes": 20481, "modifiedTime": 1755945300000, "isNew": true}], "result": "2 file(s) delivered", "timing": {"total": 812}}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/SftpWatchPoll" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"watchId": "sfw_0123456789abcdef0123456789abcdef"}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""watchId"": ""sfw_0123456789abcdef0123456789abcdef""}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/SftpWatchPoll", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/SftpWatchPoll"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"watchId": "sfw_0123456789abcdef0123456789abcdef"}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/SftpWatchPoll
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "watchId": "sfw_0123456789abcdef0123456789abcdef"
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/SftpWatchPoll"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

Storage: Download File

POST /api/StorageDownloadFile 1 token

Downloads one file from the DocButterfly folder in a person's own Dropbox, Google Drive, OneDrive or Box, through a registry connection of type 'dropbox', 'googledrive', 'msgraphdrive' or 'box' owned by the calling client. One generic step serves all four providers: the connection decides which one, so a workflow written against Dropbox runs unchanged against a Drive.

THE GRANT REACHES ONE FOLDER AND NOTHING ELSE: the app folder on Dropbox, Files.ReadWrite.AppFolder on OneDrive, drive.file on Google Drive. There is no way to address a file the person keeps anywhere else, which is the point of the design rather than a check we make. BOX IS THE EXCEPTION: its only grant is the whole account, so on Box it IS a check we make, before any byte moves: an id whose file Box does not place inside the DocButterfly folder is a 404, whether or not it names a real file elsewhere. On Box the bytes come from the signed link Box redirects to, fetched with no Authorization header. Files up to 32 MB, the same wire budget BlobReadFile and SftpDownloadFile enforce; the size is checked from the declared length before the body moves and again on what arrived, because a provider that under-reports would otherwise slip a large file past the cap. On OneDrive the bytes are fetched from the short-lived pre-authenticated link Graph returns, with no Authorization header, so our bearer never travels to the content host. A file that is not there is a 404 STORAGE_FILE_NOT_FOUND; a lapsed sign-in is STORAGE_REAUTH_REQUIRED and needs a person to sign in again, not a retry. Reading is not a side effect. Uses a saved storage connection, so it is not available in the anonymous playground or the interactive builder Run.
Parameters
NameTypeRequiredDescription
connectionId string required Registry connection id (conn_…) of type 'dropbox', 'googledrive', 'msgraphdrive' or 'box', owned by the calling client (manage/connections). Any other type is refused by name with STORAGE_BAD_CONNECTION_TYPE before a token is spent
id string optional The opaque file id StorageListFolder returns. Preferred: it works on all four providers and survives a rename. Give this or path
path string optional The file name in the DocButterfly folder, as a convenience when you already know it. Give this or id
Request Example
JSON
{"connectionId": "conn_0123456789abcdef0123456789abcdef", "id": "01ABCDEF2GHIJK3LMNOP4QRSTU"}
Response Example
JSON
{"success": true, "provider": "msgraphdrive", "connectionId": "conn_0123456789abcdef0123456789abcdef", "name": "invoice-9.pdf", "sizeBytes": 20481, "modifiedTime": 1758049800000, "contentType": "application/pdf", "contentBase64": "JVBERi0xLjcK..."}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/StorageDownloadFile" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"connectionId": "conn_0123456789abcdef0123456789abcdef", "id": "01ABCDEF2GHIJK3LMNOP4QRSTU"}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""connectionId"": ""conn_0123456789abcdef0123456789abcdef"", ""id"": ""01ABCDEF2GHIJK3LMNOP4QRSTU""}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/StorageDownloadFile", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/StorageDownloadFile"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"connectionId": "conn_0123456789abcdef0123456789abcdef", "id": "01ABCDEF2GHIJK3LMNOP4QRSTU"}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/StorageDownloadFile
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "connectionId": "conn_0123456789abcdef0123456789abcdef",
│      "id": "01ABCDEF2GHIJK3LMNOP4QRSTU"
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/StorageDownloadFile"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

Storage: List Folder

POST /api/StorageListFolder 1 token

Lists one page of the DocButterfly folder in a person's own Dropbox, Google Drive, OneDrive or Box, through a registry connection of type 'dropbox', 'googledrive', 'msgraphdrive' or 'box'. This is the step that makes 'process everything that landed in my Drive' writable, and the id it returns is what StorageDownloadFile takes.

CAPPED AT 500 ENTRIES PER PAGE, and unlike the Blob and SFTP listings this one is not truncated with a flag: 'cursor' carries the position and 'more' says whether there is another page, so a folder larger than a page is fully readable rather than silently cut. THE CURSOR IS OURS TO ISSUE AND YOURS TO HAND BACK UNCHANGED. It is refused otherwise, by name, because a OneDrive cursor is a URL this service then fetches with the connection's token, and a caller who could write that URL would be choosing the resource rather than paging ours. 'id' is the opaque, rename-proof locator every provider gives and the one to feed to StorageDownloadFile; 'path' is a display value and is a bare name on Google Drive and OneDrive, which have no path space inside the app folder, and a name relative to the DocButterfly folder on Box. Reading is not a side effect. Uses a saved storage connection, so it is not available in the anonymous playground or the interactive builder Run.
Parameters
NameTypeRequiredDescription
connectionId string required Registry connection id (conn_…) of type 'dropbox', 'googledrive', 'msgraphdrive' or 'box', owned by the calling client (manage/connections). Any other type is refused by name with STORAGE_BAD_CONNECTION_TYPE before a token is spent
path string optional A subfolder of the DocButterfly folder. Omit for the folder itself (default: the DocButterfly folder)
cursor string optional The cursor the previous page returned, passed back unchanged, to read the next page. A cursor this service did not issue is refused with STORAGE_CURSOR_NOT_ALLOWED
limit number optional How many entries to return. Anything above 500 is clamped to 500 and the rest is paged with the cursor (default: 500)
Request Example
JSON
{"connectionId": "conn_0123456789abcdef0123456789abcdef", "limit": 100}
Response Example
JSON
{"success": true, "provider": "dropbox", "connectionId": "conn_0123456789abcdef0123456789abcdef", "entries": [{"id": "id:aBcD1234", "name": "invoice-9.pdf", "path": "/invoice-9.pdf", "sizeBytes": 20481, "modifiedTime": 1758049800000, "isFolder": false}], "count": 1, "cursor": null, "more": false}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/StorageListFolder" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"connectionId": "conn_0123456789abcdef0123456789abcdef", "limit": 100}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""connectionId"": ""conn_0123456789abcdef0123456789abcdef"", ""limit"": 100}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/StorageListFolder", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/StorageListFolder"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"connectionId": "conn_0123456789abcdef0123456789abcdef", "limit": 100}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/StorageListFolder
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "connectionId": "conn_0123456789abcdef0123456789abcdef",
│      "limit": 100
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/StorageListFolder"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

Storage: Poll Watcher

POST /api/StorageWatchPoll 1 token

Runs one storage watcher's check immediately: lists the DocButterfly folder in the connected Dropbox, Google Drive, OneDrive or Box, works out what is new or changed since the last poll, and starts the registered workflow for each file. The same thing the watcher's timer does on its schedule; this is the button for 'check now', and for seeing what a watcher would pick up before you leave it running.

BILLED PER FILE DELIVERED, not per poll: one token for each file handed to your workflow, with a floor of one token for a poll you asked for yourself. The watcher's own timer charges nothing when it finds nothing, however often it runs. The workflow bills its own steps separately; this is the delivery, not the processing. NONE OF THE PROVIDERS' CHANGE FEEDS IS USED, because they are different mechanisms with different failure modes, so the watcher remembers each file's id, size and modification time and treats anything new or altered as changed, which also catches a file re-uploaded with its original timestamp. Keying on the provider's opaque id rather than the name means a rename does not re-deliver and two files cannot collide. THE FIRST POLL DELIVERS NOTHING: it records what is already in the folder, so pointing a watcher at a folder that already holds files does not reprocess them, and it records that it did so even when the folder was empty. A deleted file is dropped from that memory and never delivered; there is nothing left to fetch. At most 30 files move per poll, the same ceiling the per-unit billing machinery can charge for, and 'more: true' says a burst is still draining. A folder holding more than 500 files SUSPENDS the watcher with a reason instead of being half-watched. Each file arrives as its listing row plus the connectionId, so the workflow's first step can be StorageDownloadFile; the bytes are never pushed into the pipeline input. Uses a saved storage connection, so it is not available in the anonymous playground or the interactive builder Run.
Parameters
NameTypeRequiredDescription
watchId string required The watcher to poll (stw_…), created through manage/storage/{clientId}/watchers
dryRun boolean optional Report what WOULD be delivered without starting any workflow and without charging. The folder is not marked as seen either, so a real poll afterwards still finds the same files (default: false)
Request Example
JSON
{"watchId": "stw_0123456789abcdef0123456789abcdef"}
Response Example
JSON
{"success": true, "watchId": "stw_0123456789abcdef0123456789abcdef", "source": "storage", "provider": "googledrive", "workflowSlug": "invoice-intake", "dryRun": false, "baseline": false, "changed": 2, "delivered": 2, "more": false, "failures": [], "files": [{"id": "1AbCdEfGhIjKlMnOpQrStUv", "name": "invoice-9.pdf", "path": "invoice-9.pdf", "sizeBytes": 20481, "modifiedTime": 1758049800000, "isNew": true}], "result": "2 file(s) delivered", "timing": {"total": 812}}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/StorageWatchPoll" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"watchId": "stw_0123456789abcdef0123456789abcdef"}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""watchId"": ""stw_0123456789abcdef0123456789abcdef""}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/StorageWatchPoll", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/StorageWatchPoll"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"watchId": "stw_0123456789abcdef0123456789abcdef"}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/StorageWatchPoll
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "watchId": "stw_0123456789abcdef0123456789abcdef"
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/StorageWatchPoll"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

Blob: Poll Watcher

POST /api/BlobWatchPoll 1 token

Runs one Azure Blob watcher's check immediately: lists the watched folder of the container, works out what is new or changed since the last poll, and starts the registered workflow for each file. The same thing the watcher's timer does on its schedule; this is the button for 'check now', and for seeing what a watcher would pick up before you leave it running.

BILLED PER FILE DELIVERED, not per poll: one token for each file handed to your workflow, with a floor of one token for a poll you asked for yourself. The watcher's own timer charges nothing when it finds nothing, however often it runs. The workflow bills its own steps separately; this is the delivery, not the processing. The watcher remembers each blob's ETag, size and last-modified time and treats anything new or altered as changed. THE FIRST POLL DELIVERS NOTHING: it records what is already in the folder, even when the folder is empty. A deleted blob is dropped from that memory and never delivered. ONLY THE FOLDER ITSELF IS WATCHED: blobs in sub-folders below the prefix are not delivered, so a workflow that writes its output beside the file it was handed does not trigger itself. Folder placeholders and Data Lake directories are never delivered. At most 30 files move per poll and 'more: true' says a burst is still draining. A folder holding more than 500 entries SUSPENDS the watcher with a reason instead of being half-watched. Each file arrives as its listing row plus the connectionId and the container, and file.path is exactly what BlobReadFile takes; the bytes are never pushed into the pipeline input. Uses a saved blob connection, so it is not available in the anonymous playground or the interactive builder Run.
Parameters
NameTypeRequiredDescription
watchId string required The watcher to poll (bbw_…), created through manage/blob/{clientId}/watchers
dryRun boolean optional Report what WOULD be delivered without starting any workflow and without charging. The folder is not marked as seen either, so a real poll afterwards still finds the same files (default: false)
Request Example
JSON
{"watchId": "bbw_0123456789abcdef0123456789abcdef"}
Response Example
JSON
{"success": true, "watchId": "bbw_0123456789abcdef0123456789abcdef", "source": "blob", "workflowSlug": "invoice-intake", "container": "invoices", "prefix": "inbound/", "dryRun": false, "baseline": false, "changed": 1, "delivered": 1, "more": false, "failures": [], "files": [{"name": "inv-9.pdf", "path": "inbound/inv-9.pdf", "sizeBytes": 20481, "modifiedTime": 1758049800000, "etag": "\"0x8DCABCDEF\"", "isNew": true}], "result": "1 file(s) delivered", "timing": {"total": 612}}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/BlobWatchPoll" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"watchId": "bbw_0123456789abcdef0123456789abcdef"}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""watchId"": ""bbw_0123456789abcdef0123456789abcdef""}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/BlobWatchPoll", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/BlobWatchPoll"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"watchId": "bbw_0123456789abcdef0123456789abcdef"}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/BlobWatchPoll
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "watchId": "bbw_0123456789abcdef0123456789abcdef"
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/BlobWatchPoll"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

S3: Poll Watcher

POST /api/S3WatchPoll 1 token

Runs one S3 watcher's check immediately: lists the watched folder of the bucket, works out what is new or changed since the last poll, and starts the registered workflow for each file. Works with any S3-compatible provider the S3 connection serves. The same thing the watcher's timer does on its schedule; this is the button for 'check now', and for seeing what a watcher would pick up before you leave it running.

BILLED PER FILE DELIVERED, not per poll: one token for each file handed to your workflow, with a floor of one token for a poll you asked for yourself. The watcher's own timer charges nothing when it finds nothing, however often it runs. The workflow bills its own steps separately; this is the delivery, not the processing. The watcher remembers each object's ETag, size and last-modified time and treats anything new or altered as changed. THE FIRST POLL DELIVERS NOTHING: it records what is already in the folder, even when the folder is empty. A deleted object is dropped from that memory and never delivered. ONLY THE FOLDER ITSELF IS WATCHED: objects under deeper prefixes are not delivered, so a workflow that writes its output beside the file it was handed does not trigger itself. Folder marker objects are never delivered. At most 30 files move per poll and 'more: true' says a burst is still draining. A folder holding more than 500 entries SUSPENDS the watcher with a reason instead of being half-watched. Each file arrives as its listing row plus the connectionId and the bucket, and file.path is exactly what S3GetObject takes as its key; the bytes are never pushed into the pipeline input. Uses a saved S3 connection, so it is not available in the anonymous playground or the interactive builder Run.
Parameters
NameTypeRequiredDescription
watchId string required The watcher to poll (s3w_…), created through manage/s3/{clientId}/watchers
dryRun boolean optional Report what WOULD be delivered without starting any workflow and without charging. The folder is not marked as seen either, so a real poll afterwards still finds the same files (default: false)
Request Example
JSON
{"watchId": "s3w_0123456789abcdef0123456789abcdef"}
Response Example
JSON
{"success": true, "watchId": "s3w_0123456789abcdef0123456789abcdef", "source": "s3", "workflowSlug": "invoice-intake", "bucket": "acme-invoices", "prefix": "inbound/", "dryRun": false, "baseline": false, "changed": 1, "delivered": 1, "more": false, "failures": [], "files": [{"name": "inv-9.pdf", "path": "inbound/inv-9.pdf", "sizeBytes": 20481, "modifiedTime": 1758049800000, "etag": "\"9b2cf535f27731c974343645a3985328\"", "isNew": true}], "result": "1 file(s) delivered", "timing": {"total": 540}}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/S3WatchPoll" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"watchId": "s3w_0123456789abcdef0123456789abcdef"}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""watchId"": ""s3w_0123456789abcdef0123456789abcdef""}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/S3WatchPoll", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/S3WatchPoll"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"watchId": "s3w_0123456789abcdef0123456789abcdef"}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/S3WatchPoll
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "watchId": "s3w_0123456789abcdef0123456789abcdef"
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/S3WatchPoll"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

Table: Poll Watcher

POST /api/TableWatchPoll 1 token

Runs one table watcher's check immediately: reads the rows of a Dataverse table or a SQL Server, PostgreSQL or MySQL table that come after the watcher's last position, and starts the registered workflow for each one. The same thing the watcher's timer does on its schedule; this is the button for 'check now', and for seeing what a watcher would pick up before you leave it running.

BILLED PER ROW DELIVERED, not per poll: one token for each row handed to your workflow, with a floor of one token for a poll you asked for yourself. The watcher's own timer charges nothing when it finds nothing, however often it runs. The workflow bills its own steps separately; this is the delivery, not the processing. IT POLLS A CURSOR COLUMN, NOT A CHANGE FEED: a column that only ever increases (a created or modified timestamp, or an increasing id) plus the primary key as a tie-breaker, so it works with the same read-only login SqlQueryRows and DataverseQueryRows use, and needs no CDC, replication or change-tracking setup on your database. On Dataverse the cursor defaults to createdon (mode 'new') or modifiedon (mode 'changed') and the key to the table's id; on SQL you name the cursor column. THE FIRST POLL DELIVERS NOTHING: it records the table's current high-water mark, so a watcher on a table that already holds millions of rows does not reprocess them. A row is delivered once per advance of its cursor, so in 'changed' mode a row modified again is delivered again; a DELETED row is never delivered, because there is nothing left to read. At most 30 rows move per poll, the ceiling the per-unit billing can charge for, and 'more: true' says a burst is still draining. Timestamps are compared a whole second at a time and walked by key inside the second, because the databases hold more precision than they hand back; Dataverse seconds are read once they are five seconds old. The response lists each row's position, not its content. Each row arrives in the workflow as $input.row with its own $input.cursor, plus the connectionId, table and mode. A cursor column that is missing or cannot be ordered, a missing key, or a query the connection refuses SUSPENDS the watcher with a reason. Uses a saved data connection, so it is not available in the anonymous playground or the interactive builder Run.
Parameters
NameTypeRequiredDescription
watchId string required The watcher to poll (tbw_…), created through manage/table/{clientId}/watchers
dryRun boolean optional Report what WOULD be delivered without starting any workflow and without the per-row charge. The watcher's position does not move either, so a real poll afterwards still finds the same rows (default: false)
Request Example
JSON
{"watchId": "tbw_0123456789abcdef0123456789abcdef"}
Response Example
JSON
{"success": true, "watchId": "tbw_0123456789abcdef0123456789abcdef", "source": "table", "workflowSlug": "order-intake", "table": "sales.orders", "mode": "new", "dryRun": false, "baseline": false, "changed": 2, "delivered": 2, "more": false, "failures": [], "rows": [{"cursorValue": "2026-09-26T14:05:12.000Z", "key": 10482}, {"cursorValue": "2026-09-26T14:05:40.000Z", "key": 10483}], "result": "2 row(s) delivered", "timing": {"total": 640}}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/TableWatchPoll" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"watchId": "tbw_0123456789abcdef0123456789abcdef"}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""watchId"": ""tbw_0123456789abcdef0123456789abcdef""}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/TableWatchPoll", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/TableWatchPoll"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"watchId": "tbw_0123456789abcdef0123456789abcdef"}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/TableWatchPoll
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "watchId": "tbw_0123456789abcdef0123456789abcdef"
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/TableWatchPoll"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

Service Bus: Receive Messages

POST /api/ServiceBusReceiveMessages 1 token

Peeks messages from an Azure Service Bus queue or topic subscription via a registry connection of type 'servicebus'. PEEK-LOCK ONLY, AND IT NEVER SETTLES: nothing is consumed and nothing is removed. The connection holds the namespace and the connection string or SAS token — neither ever appears in the workflow definition.

READ THIS BEFORE BUILDING ON IT. The lock EXPIRES and the same messages come back on the next call — this endpoint has no complete, abandon or dead-letter, and lockNotice says so in the payload rather than only in these docs. A peek still increments each message's deliveryCount, and Service Bus dead-letters a message once that passes the entity's MaxDeliveryCount, so repeatedly peeking messages nothing ever processes will eventually move them to the dead-letter queue; deliveryCount is returned so a caller can see it coming. A settling (destructive) receive would consume the message, which is a different price and a different op key, and is deliberately not this endpoint. Exactly one field carries each body — 'body' for text, 'bodyBase64' for bytes, with 'bodyEncoding' naming the choice. Billing is flat per CALL, not per message: a call that finds an empty entity still costs its token, because the poll is the work. Reading is not a side effect: a retry peeks again and duplicates nothing.
Parameters
NameTypeRequiredDescription
connectionId string required Registry connection id (conn_…) of type 'servicebus', owned by the calling client (manage/connections)
queueOrTopic string optional Overrides the connection's default entity. May be hierarchical (e.g. 'orders/eu', 'orders/subscriptions/eu')
subscription string optional Subscription name, when the entity is a topic. A topic itself cannot be received from — only its subscriptions can
maxMessages number optional How many messages to peek, 1-32. The REST peek-lock takes ONE message per request, so this is a loop and therefore a wall-clock cap as much as a size cap (default: 10)
waitSeconds number optional How long Service Bus holds each request open waiting for a message, 0-30 (default: 5)
Request Example
JSON
{"connectionId": "conn_0123456789abcdef0123456789abcdef", "queueOrTopic": "orders", "maxMessages": 10, "waitSeconds": 5}
Response Example
JSON
{"success": true, "received": 1, "namespace": "acme.servicebus.windows.net", "entity": "orders", "entityType": "queue", "lockMode": "peek-lock", "settled": false, "drained": true, "truncated": false, "sizeBytes": 118, "messages": [{"index": 0, "messageId": "4f2a…", "deliveryCount": 1, "lockToken": "9c1e…", "lockedUntilUtc": "2026-08-23T10:16:00Z", "lockSecondsRemaining": 55, "contentType": "application/json", "sizeBytes": 118, "bodyEncoding": "text", "body": "{\"orderId\": 42}"}]}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/ServiceBusReceiveMessages" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"connectionId": "conn_0123456789abcdef0123456789abcdef", "queueOrTopic": "orders", "maxMessages": 10, "waitSeconds": 5}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""connectionId"": ""conn_0123456789abcdef0123456789abcdef"", ""queueOrTopic"": ""orders"", ""maxMessages"": 10, ""waitSeconds"": 5}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/ServiceBusReceiveMessages", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/ServiceBusReceiveMessages"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"connectionId": "conn_0123456789abcdef0123456789abcdef", "queueOrTopic": "orders", "maxMessages": 10, "waitSeconds": 5}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/ServiceBusReceiveMessages
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "connectionId": "conn_0123456789abcdef0123456789abcdef",
│      "queueOrTopic": "orders",
│      "maxMessages": 10,
│      "waitSeconds": 5
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/ServiceBusReceiveMessages"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

S3: Get Object

POST /api/S3GetObject 1 token

Reads an object out of an Amazon S3 bucket — or any S3-compatible store (MinIO, Cloudflare R2, Wasabi, Backblaze B2, Google Cloud Storage's interoperability API) — via a registry connection of type 's3' owned by the calling client. S3 speaks its own signed HTTP protocol, so it cannot be expressed as a 'rest' connection and RestRequest does not cover it. The connection holds the bucket, region, optional endpoint, and the access key pair; none of them ever appear in the workflow definition.

EXACTLY ONE FIELD CARRIES THE PAYLOAD, and 'encoding' names which — 'content' for text, 'contentBase64' for bytes, never both. Override the sniff when the customer's own metadata is wrong: a PDF mislabeled text/plain would otherwise be decoded as UTF-8 and come back corrupt. ONE GET, not HEAD-then-GET: S3 serves a single GET from one consistent object version, and a preflight HEAD would break a least-privilege credential that carries s3:GetObject without s3:ListBucket. The 32 MB cap is enforced on the response's Content-Length BEFORE the body is read, and the stream is canceled rather than drained. The credential is used only as HMAC key material for the SigV4 signature — the secret access key itself never reaches the wire, a log line, or an error. Reading is not a side effect: a retry costs a second read. Not available in the anonymous playground or interactive builder Run.
Parameters
NameTypeRequiredDescription
connectionId string required Registry connection id (conn_…) of type 's3', owned by the calling client (manage/connections)
key string required Object key, optionally with folder-style segments (e.g. 'invoices/2026/inv-1.pdf'). Max 1024 characters; no leading slash, no '..', no control characters
bucket string optional Overrides the connection's default bucket. S3 naming rules: 3-63 lowercase alphanumerics, dots and dashes
encoding string optional Which payload field to answer with. 'auto' decides from the object's own content type; 'text' forces UTF-8 text; 'base64' forces bytes (default: auto) (one of: auto, text, base64)
Request Example
JSON
{"connectionId": "conn_0123456789abcdef0123456789abcdef", "key": "invoices/2026/inv-1.pdf"}
Response Example
JSON
{"success": true, "bucket": "acme-docs", "key": "invoices/2026/inv-1.pdf", "url": "https://acme-docs.s3.us-east-1.amazonaws.com/invoices/2026/inv-1.pdf", "contentType": "application/pdf", "sizeBytes": 20481, "etag": "\"9b2c…\"", "lastModified": "2026-08-23T10:15:00.000Z", "encoding": "base64", "contentBase64": "JVBERi0xLjcK...", "timing": {"total": 143}}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/S3GetObject" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"connectionId": "conn_0123456789abcdef0123456789abcdef", "key": "invoices/2026/inv-1.pdf"}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""connectionId"": ""conn_0123456789abcdef0123456789abcdef"", ""key"": ""invoices/2026/inv-1.pdf""}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/S3GetObject", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/S3GetObject"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"connectionId": "conn_0123456789abcdef0123456789abcdef", "key": "invoices/2026/inv-1.pdf"}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/S3GetObject
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "connectionId": "conn_0123456789abcdef0123456789abcdef",
│      "key": "invoices/2026/inv-1.pdf"
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/S3GetObject"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

S3: List Objects

POST /api/S3ListObjects 1 token

Lists the objects under a prefix in an S3 bucket (ListObjectsV2) via a registry connection of type 's3'. The discovery half of the S3 source, and the same connection type reaches every S3-compatible provider.

CAPPED AT 500 OBJECTS with truncated:true when the cap bites — the same convention every discovery endpoint here uses. There is no contentType on a listing row and that is S3's doing, not an omission: ListObjectsV2 does not return one, and reconstructing it would be a HEAD per object — N requests for one token — so the field is absent rather than fabricated. 'storageClass' takes its place, and it is the field that decides whether a Get will even succeed (a Glacier object will not). There is no continuation token: truncated:true is the honest signal that a narrower prefix is needed. Reading is not a side effect.
Parameters
NameTypeRequiredDescription
connectionId string required Registry connection id (conn_…) of type 's3', owned by the calling client (manage/connections)
bucket string optional Overrides the connection's default bucket. S3 naming rules: 3-63 lowercase alphanumerics, dots and dashes
prefix string optional The start of the object keys to list (e.g. 'invoices/2026/'). This is a key prefix, not a folder — it need not end at a '/'. Omit to list the whole bucket
Request Example
JSON
{"connectionId": "conn_0123456789abcdef0123456789abcdef", "bucket": "acme-docs", "prefix": "invoices/2026/"}
Response Example
JSON
{"success": true, "bucket": "acme-docs", "prefix": "invoices/2026/", "objects": [{"key": "invoices/2026/inv-1.pdf", "sizeBytes": 20481, "etag": "\"9b2c…\"", "lastModified": "2026-08-23T10:15:00.000Z", "storageClass": "STANDARD"}], "count": 1, "truncated": false, "timing": {"total": 164}}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/S3ListObjects" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"connectionId": "conn_0123456789abcdef0123456789abcdef", "bucket": "acme-docs", "prefix": "invoices/2026/"}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""connectionId"": ""conn_0123456789abcdef0123456789abcdef"", ""bucket"": ""acme-docs"", ""prefix"": ""invoices/2026/""}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/S3ListObjects", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/S3ListObjects"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"connectionId": "conn_0123456789abcdef0123456789abcdef", "bucket": "acme-docs", "prefix": "invoices/2026/"}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/S3ListObjects
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "connectionId": "conn_0123456789abcdef0123456789abcdef",
│      "bucket": "acme-docs",
│      "prefix": "invoices/2026/"
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/S3ListObjects"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

SharePoint: Get File

POST /api/SpGetFile 1 token

Pulls a document out of a SharePoint document library (or a OneDrive for Business drive — the same Graph surface) via a registry connection of type 'sharepoint'. App-only Microsoft Graph with Sites.Selected: the app reaches only the sites a SharePoint administrator has explicitly granted it, never the tenant. Returns the bytes plus the library's own columns for that document, which is what makes 'extract the vendor with Document AI, then tag the invoice' one chain instead of two integrations.

EXACTLY ONE ADDRESSING MODE PER CALL, and supplying two is refused rather than resolved by a precedence rule: itemId and path can name different documents, and silently preferring one would hand you a file you did not ask for. EXACTLY ONE FIELD CARRIES THE PAYLOAD, and 'encoding' names which — 'content' for text, 'contentBase64' for bytes, never both. The 32 MB cap is enforced on the document's reported size BEFORE a byte transfers and re-checked on what arrived, because metadata and content are two round trips. There is no 4 MB boundary on a download: that limit belongs to simple UPLOAD, and a download of any size is one call. The pre-authenticated download URL Graph redirects to is never returned — it needs no credential, so handing it out would hand out an unauthenticated copy of your document. A site the app has not been granted answers 403 with the grant instructions, which is a different failure from 401 'the app was never consented' and from 404 'no such file'; the three are kept apart on purpose. Reading is not a side effect: a retry costs a second read. Not available in the anonymous playground or interactive builder Run.
Parameters
NameTypeRequiredDescription
connectionId string required Registry connection id (conn_…) of type 'sharepoint', owned by the calling client (manage/connections)
itemId string optional The Graph driveItem id. Stable across a rename or a move, and what a SpListFiles row hands back. Exactly one of itemId, path or sharingUrl
path string optional Drive-relative path, e.g. 'Invoices/2026/inv-1.pdf'. Max 1024 characters; no leading slash needed, no '..', no backslashes. Exactly one of itemId, path or sharingUrl
sharingUrl string optional A link copied from SharePoint's Share dialog. Carries its own site and drive, so the connection's site is not consulted. Exactly one of itemId, path or sharingUrl
siteUrl string optional Address a different site than the connection's default. Still bound by the same grant — an ungranted site is refused
driveId string optional Address a specific document library by id, overriding the connection's driveId/libraryName
libraryName string optional Address a document library by name (e.g. 'Contracts'), matched case-insensitively. Omit for the site's default library
encoding string optional Which payload field to answer with. 'auto' decides from the document's own content type; 'text' forces UTF-8 text; 'base64' forces bytes (default: auto) (one of: auto, text, base64)
includeFields boolean optional Return the library columns for this document as listItem.fields. One extra Graph call; set false when fetching many files and you only want bytes (default: true)
Request Example
JSON
{"connectionId": "conn_0123456789abcdef0123456789abcdef", "path": "Invoices/2026/inv-1.pdf"}
Response Example
JSON
{"success": true, "siteId": "contoso.sharepoint.com,1a2b…,3c4d…", "driveId": "b!aBcD…", "itemId": "01ABCDEF…", "name": "inv-1.pdf", "path": "Invoices/2026/inv-1.pdf", "contentType": "application/pdf", "sizeBytes": 20481, "eTag": "\"{GUID},3\"", "lastModifiedDateTime": "2026-09-01T10:15:00Z", "webUrl": "https://contoso.sharepoint.com/sites/Finance/Shared%20Documents/Invoices/2026/inv-1.pdf", "addressedBy": "path", "encoding": "base64", "contentBase64": "JVBERi0xLjcK...", "listItem": {"id": "42", "fields": {"Title": "Acme March", "Vendor": "Acme Ltd", "Approved": false}}}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/SpGetFile" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"connectionId": "conn_0123456789abcdef0123456789abcdef", "path": "Invoices/2026/inv-1.pdf"}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""connectionId"": ""conn_0123456789abcdef0123456789abcdef"", ""path"": ""Invoices/2026/inv-1.pdf""}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/SpGetFile", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/SpGetFile"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"connectionId": "conn_0123456789abcdef0123456789abcdef", "path": "Invoices/2026/inv-1.pdf"}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/SpGetFile
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "connectionId": "conn_0123456789abcdef0123456789abcdef",
│      "path": "Invoices/2026/inv-1.pdf"
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/SpGetFile"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

SharePoint: List Sites

POST /api/SpListSites 1 token

Resolves the SharePoint site a connection points at, and — where the app registration allows it — searches for others. The first screen of a SharePoint picker.

SEARCHING FOR SITES DOES NOT WORK UNDER Sites.Selected, and this endpoint says so rather than failing silently. The search enumerates the tenant, which is exactly what Sites.Selected exists to prevent, so Microsoft Graph refuses it even for sites the app has been granted. When that happens the answer is searchSupported:false plus a note telling you to paste a site URL — not an error — and the connection's own site is still returned so a picker has something to show. An app registration that additionally holds Sites.Read.All can search; the capability is probed on each call, never assumed. A SEARCH IS CAPPED AT 500 SITES: the result pages are read up to that cap, and truncated:true means more sites matched than came back, so narrow the search term rather than treat the list as complete. There is no cursor. Resolving one site (no search, or a siteUrl) always answers truncated:false. Reading is not a side effect.
Parameters
NameTypeRequiredDescription
connectionId string required Registry connection id (conn_…) of type 'sharepoint', owned by the calling client (manage/connections)
siteUrl string optional Resolve this site instead of the connection's default. Paste it from your browser's address bar
search string optional A site name to search for. Attempted, not assumed: under Sites.Selected the search is refused and the answer says so
Request Example
JSON
{"connectionId": "conn_0123456789abcdef0123456789abcdef"}
Response Example
JSON
{"success": true, "sites": [{"siteId": "contoso.sharepoint.com,1a2b…,3c4d…", "displayName": "Finance", "webUrl": "https://contoso.sharepoint.com/sites/Finance", "isConnectionDefault": true}], "count": 1, "truncated": false, "searchSupported": null}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/SpListSites" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"connectionId": "conn_0123456789abcdef0123456789abcdef"}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""connectionId"": ""conn_0123456789abcdef0123456789abcdef""}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/SpListSites", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/SpListSites"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"connectionId": "conn_0123456789abcdef0123456789abcdef"}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/SpListSites
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "connectionId": "conn_0123456789abcdef0123456789abcdef"
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/SpListSites"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

SharePoint: List Libraries

POST /api/SpListLibraries 1 token

Lists the document libraries (drives) in a SharePoint site. The second screen of a SharePoint picker, and how you find the library name or drive id every other SharePoint call takes.

A site with one library needs none of this — omit libraryName everywhere and the site's default library is used. It matters on a site with several: 'Contracts' and 'Invoices' are different drives with different permissions, and naming the wrong one is a 404 nobody can explain without this list. CAPPED AT 500 LIBRARIES with truncated:true when a site has more. Every page Microsoft Graph returns is read up to that cap, so a library past the first page is listed, and libraryName finds it on every other SharePoint call. There is no cursor. Reading is not a side effect.
Parameters
NameTypeRequiredDescription
connectionId string required Registry connection id (conn_…) of type 'sharepoint', owned by the calling client (manage/connections)
siteUrl string optional List the libraries of this site instead of the connection's default
Request Example
JSON
{"connectionId": "conn_0123456789abcdef0123456789abcdef"}
Response Example
JSON
{"success": true, "siteId": "contoso.sharepoint.com,1a2b…,3c4d…", "siteUrl": "https://contoso.sharepoint.com/sites/Finance", "libraries": [{"driveId": "b!aBcD…", "name": "Documents", "driveType": "documentLibrary", "webUrl": "https://contoso.sharepoint.com/sites/Finance/Shared%20Documents"}], "count": 1, "truncated": false, "timing": {"total": 261}}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/SpListLibraries" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"connectionId": "conn_0123456789abcdef0123456789abcdef"}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""connectionId"": ""conn_0123456789abcdef0123456789abcdef""}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/SpListLibraries", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/SpListLibraries"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"connectionId": "conn_0123456789abcdef0123456789abcdef"}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/SpListLibraries
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "connectionId": "conn_0123456789abcdef0123456789abcdef"
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/SpListLibraries"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

SharePoint: List Files

POST /api/SpListFiles 1 token

Enumerates a folder in a SharePoint document library, with real paging and an optional recursive walk. Returns ids, paths and metadata WITHOUT content, so it is cheap to run over a whole library and then fan out to SpGetFile for the documents you actually want.

PAGING IS REAL: a page comes back with nextCursor, and passing it as 'cursor' continues from there, so a 40,000-file library is walkable instead of being met with 'narrow your prefix'. The cursor is opaque and is validated to be one this endpoint issued — a URL of your own is refused. RECURSION IS BUDGETED, not unbounded: SharePoint has no 'give me the whole subtree' call, so 'recursive' is a breadth-first walk capped at 25 Graph requests, and when the budget or the 500-item cap bites you get truncated:true and what was found, never a partial that looks complete. 'cursor' and 'recursive' cannot be combined — a continuation token addresses one folder's page and a walk does not have one. Each item's 'path' is exactly what SpGetFile takes back as 'path', so a row round-trips without editing. No content is returned. Reading is not a side effect.
Parameters
NameTypeRequiredDescription
connectionId string required Registry connection id (conn_…) of type 'sharepoint', owned by the calling client (manage/connections)
folderPath string optional Drive-relative folder, e.g. 'Invoices/2026'. Omit for the library root. No '..', no backslashes (default: the library root)
recursive boolean optional Walk sub-folders too. Budgeted — see the notes (default: false)
filesOnly boolean optional Drop folders from the listing (default: false)
limit number optional How many items to return. Clamped to 500 (default: 200)
cursor string optional The nextCursor from a previous page. Must be a token this endpoint issued
siteUrl string optional Address a different site than the connection's default
driveId string optional Address a specific document library by id
libraryName string optional Address a document library by name, matched case-insensitively
Request Example
JSON
{"connectionId": "conn_0123456789abcdef0123456789abcdef", "folderPath": "Invoices/2026", "filesOnly": true}
Response Example
JSON
{"success": true, "siteId": "contoso.sharepoint.com,1a2b…,3c4d…", "driveId": "b!aBcD…", "folderPath": "Invoices/2026", "recursive": false, "items": [{"itemId": "01ABCDEF…", "driveId": "b!aBcD…", "name": "inv-1.pdf", "path": "Invoices/2026/inv-1.pdf", "isFolder": false, "sizeBytes": 20481, "contentType": "application/pdf", "eTag": "\"{GUID},3\"", "lastModifiedDateTime": "2026-09-01T10:15:00Z", "webUrl": "https://contoso.sharepoint.com/…/inv-1.pdf"}], "count": 1, "truncated": false, "graphReads": 1}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/SpListFiles" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"connectionId": "conn_0123456789abcdef0123456789abcdef", "folderPath": "Invoices/2026", "filesOnly": true}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""connectionId"": ""conn_0123456789abcdef0123456789abcdef"", ""folderPath"": ""Invoices/2026"", ""filesOnly"": true}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/SpListFiles", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/SpListFiles"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"connectionId": "conn_0123456789abcdef0123456789abcdef", "folderPath": "Invoices/2026", "filesOnly": true}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/SpListFiles
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "connectionId": "conn_0123456789abcdef0123456789abcdef",
│      "folderPath": "Invoices/2026",
│      "filesOnly": true
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/SpListFiles"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

SharePoint: Search Files

POST /api/SpSearchFiles 1 token

Searches a SharePoint document library for a term and narrows the results by file extension and modified date. Metadata only, no content — the discovery half of 'process every invoice that arrived this week'.

THE EXTENSION AND DATE FILTERS ARE APPLIED AFTER THE FETCH, and 'scanned' is why that matters: SharePoint's drive search does not accept a filter on either, so we narrow the pages we retrieved. A very selective filter over a very large library can therefore return fewer than 'limit' with truncated:true — 'scanned: 400, count: 3' tells you we looked at 400 results and three matched, where a bare 'count: 3' would imply there are only three. Quotes and backslashes in 'query' are refused rather than escaped: a silently altered search returns the wrong documents, which is worse than a 400. No content is returned. Reading is not a side effect.
Parameters
NameTypeRequiredDescription
connectionId string required Registry connection id (conn_…) of type 'sharepoint', owned by the calling client (manage/connections)
query string required What to search for. Max 256 characters; quotes and backslashes are refused
folderPath string optional Search inside this folder only (default: the whole library)
extensions string optional Comma list or array of extensions to keep, e.g. 'pdf,docx'
modifiedAfter string optional ISO 8601 — keep items modified at or after this instant
modifiedBefore string optional ISO 8601 — keep items modified at or before this instant
filesOnly boolean optional Drop folders from the results (default: true)
limit number optional How many items to return. Clamped to 500 (default: 200)
siteUrl string optional Address a different site than the connection's default
driveId string optional Address a specific document library by id
libraryName string optional Address a document library by name, matched case-insensitively
Request Example
JSON
{"connectionId": "conn_0123456789abcdef0123456789abcdef", "query": "invoice", "extensions": "pdf", "modifiedAfter": "2026-09-01T00:00:00Z"}
Response Example
JSON
{"success": true, "siteId": "contoso.sharepoint.com,1a2b…,3c4d…", "driveId": "b!aBcD…", "folderPath": "", "query": "invoice", "items": [{"itemId": "01ABCDEF…", "name": "inv-1.pdf", "path": "Invoices/2026/inv-1.pdf", "sizeBytes": 20481, "lastModifiedDateTime": "2026-09-01T10:15:00Z"}], "count": 1, "scanned": 42, "truncated": false, "graphReads": 1, "timing": {"total": 940}}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/SpSearchFiles" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"connectionId": "conn_0123456789abcdef0123456789abcdef", "query": "invoice", "extensions": "pdf", "modifiedAfter": "2026-09-01T00:00:00Z"}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""connectionId"": ""conn_0123456789abcdef0123456789abcdef"", ""query"": ""invoice"", ""extensions"": ""pdf"", ""modifiedAfter"": ""2026-09-01T00:00:00Z""}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/SpSearchFiles", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/SpSearchFiles"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"connectionId": "conn_0123456789abcdef0123456789abcdef", "query": "invoice", "extensions": "pdf", "modifiedAfter": "2026-09-01T00:00:00Z"}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/SpSearchFiles
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "connectionId": "conn_0123456789abcdef0123456789abcdef",
│      "query": "invoice",
│      "extensions": "pdf",
│      "modifiedAfter": "2026-09-01T00:00:00Z"
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/SpSearchFiles"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

SharePoint: Poll Watcher

POST /api/SpWatchPoll 1 token

Runs one SharePoint watcher's check immediately: reads what changed in the watched folder since the last poll and starts the registered workflow for each new or changed file. The same thing the watcher's timer does on its schedule — this is the button for 'check now', and for seeing what a watcher would pick up before you leave it running.

BILLED PER FILE DELIVERED, not per poll — one token for each file handed to your workflow, with a floor of one token for a poll you asked for yourself. The watcher's own timer charges nothing when it finds nothing, however often it runs. The workflow bills its own steps separately; this is the delivery, not the processing. THE FIRST POLL OF A NEW WATCHER DELIVERS NOTHING: it establishes a baseline, so pointing a watcher at a library that already holds ten thousand files does not reprocess them. Deletions are counted and reported, never delivered — a workflow handed the id of a file that is gone would 404 on every poll. At most 30 files move per poll; a burst drains over several polls, and 'more: true' says so. A delivery failure does NOT advance the change token, so the window is re-read rather than a document being silently lost. Uses a saved SharePoint connection, so it is not available in the anonymous playground or the interactive builder Run.
Parameters
NameTypeRequiredDescription
watchId string required The watcher to poll (spw_…), created through manage/sharepoint/{clientId}/watchers
dryRun boolean optional Report what WOULD be delivered without starting any workflow and without charging. The change token is not advanced either, so a real poll afterwards still sees the same files (default: false)
Request Example
JSON
{"watchId": "spw_0123456789abcdef0123456789abcdef"}
Response Example
JSON
{"success": true, "watchId": "spw_0123456789abcdef0123456789abcdef", "workflowSlug": "invoice-intake", "dryRun": false, "baseline": false, "resync": false, "changed": 2, "delivered": 2, "deleted": 0, "more": false, "failures": [], "files": [{"itemId": "01ABCDEF…", "name": "inv-9.pdf", "path": "Invoices/2026/inv-9.pdf", "sizeBytes": 20481, "contentType": "application/pdf", "lastModifiedDateTime": "2026-09-04T08:00:00Z"}], "result": "2 file(s) delivered", "timing": {"total": 812}}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/SpWatchPoll" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"watchId": "spw_0123456789abcdef0123456789abcdef"}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""watchId"": ""spw_0123456789abcdef0123456789abcdef""}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/SpWatchPoll", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/SpWatchPoll"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"watchId": "spw_0123456789abcdef0123456789abcdef"}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/SpWatchPoll
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "watchId": "spw_0123456789abcdef0123456789abcdef"
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/SpWatchPoll"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

SharePoint: Get Metadata

POST /api/SpGetMetadata 1 token

Reads the library column values on one document in a SharePoint or OneDrive library, via a registry connection of type 'sharepoint'. These are the fields a library's views, filters and retention rules actually run on — the vendor, the invoice total, the status — not the file's own properties.

The response carries the values a person would recognize as their metadata: OData annotations and SharePoint's own underscore-prefixed internals are stripped. Hidden columns are left out of `columns` — they are not something a customer maps to. Two refusals that look alike and are not: HTTP 401 means the app registration has no admin consent, HTTP 403 means it has consent but THIS site was never granted to it. Reading is not a side effect. Not available in the anonymous playground or interactive builder Run (connection steps execute server-side via webhooks/form submissions).
Parameters
NameTypeRequiredDescription
connectionId string required Registry connection id (conn_…) of type 'sharepoint', owned by the calling client (manage/connections)
itemId string optional Graph drive item id — what SpUploadFile and SpGetFile return. Use this or path
path string optional The document's location inside the library (e.g. 'Invoices/2026/invoice-1042.pdf'). Use this or itemId
siteUrl string optional The site's browser address. Overrides the connection's site
siteId string optional Graph site id. Overrides the connection's site
driveId string optional Graph drive id of the library. Overrides the connection's library
libraryName string optional The library's display name (e.g. 'Documents'), matched within the site
includeColumns boolean optional Also return the library's column DEFINITIONS — name, display name, type, required, choices — which is what a mapping screen needs before it can propose anything. One extra Graph call, so it is opt-in (default: false)
Request Example
JSON
{"connectionId": "conn_0123456789abcdef0123456789abcdef", "siteUrl": "https://contoso.sharepoint.com/sites/Finance", "libraryName": "Documents", "path": "Invoices/2026/invoice-1042.pdf", "includeColumns": true}
Response Example
JSON
{"success": true, "itemId": "01ABC…", "name": "invoice-1042.pdf", "listItemId": "42", "fields": {"Title": "Invoice 1042", "Vendor": "Acme", "InvoiceTotal": 582.62}, "columns": [{"name": "Vendor", "displayName": "Vendor", "type": "text", "required": false}], "timing": {"total": 431}}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/SpGetMetadata" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"connectionId": "conn_0123456789abcdef0123456789abcdef", "siteUrl": "https://contoso.sharepoint.com/sites/Finance", "libraryName": "Documents", "path": "Invoices/2026/invoice-1042.pdf", "includeColumns": true}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""connectionId"": ""conn_0123456789abcdef0123456789abcdef"", ""siteUrl"": ""https://contoso.sharepoint.com/sites/Finance"", ""libraryName"": ""Documents"", ""path"": ""Invoices/2026/invoice-1042.pdf"", ""includeColumns"": true}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/SpGetMetadata", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/SpGetMetadata"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"connectionId": "conn_0123456789abcdef0123456789abcdef", "siteUrl": "https://contoso.sharepoint.com/sites/Finance", "libraryName": "Documents", "path": "Invoices/2026/invoice-1042.pdf", "includeColumns": true}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/SpGetMetadata
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "connectionId": "conn_0123456789abcdef0123456789abcdef",
│      "siteUrl": "https://contoso.sharepoint.com/sites/Finance",
│      "libraryName": "Documents",
│      "path": "Invoices/2026/invoice-1042.pdf",
│      "includeColumns": true
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/SpGetMetadata"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

DocButterfly: Read Template

POST /api/DocButterflyReadTemplate 1 token

Reads one template saved on your own DocButterfly account, by id, through a connection of type 'dbfClient' (DocButterfly, this account). The connection has no credential: the account that owns it is the account it reads, so a workflow picks the account's own templates the way it picks any other source.

THIS ACCOUNT ONLY. The template is read from the account that owns the connection, never from an account named in the request, and a connection set to another account or another address is refused by name (DBF_CONNECTION_OTHER_ACCOUNT, DBF_CONNECTION_OTHER_ADDRESS). A template that is not on this account, or was deleted, is a 404 TEMPLATE_NOT_FOUND. The answer is the template's definition: its fields, tables and, for a compose template, its format and contract. Its training state and sample files stay home. Reading is not a side effect. Uses a saved connection, so it is not available in the anonymous playground or the interactive builder Run.
Parameters
NameTypeRequiredDescription
connectionId string required Registry connection id (conn_…) of type 'dbfClient', owned by the calling client (manage/connections)
templateId string required The id of a template saved on this account
Request Example
JSON
{"connectionId": "conn_0123456789abcdef0123456789abcdef", "templateId": "3f2b8c1e-5d4a-4f6b-9c0d-1e2f3a4b5c6d"}
Response Example
JSON
{"success": true, "connectionId": "conn_0123456789abcdef0123456789abcdef", "template": {"templateId": "3f2b8c1e-5d4a-4f6b-9c0d-1e2f3a4b5c6d", "name": "Supplier invoice", "description": "Invoices from our suppliers", "documentType": "Invoice", "templateKind": "extract", "fields": [{"fieldKey": "invoiceNumber", "label": "Invoice number"}], "tables": [], "hasComposeBody": false, "createdAt": "2026-09-01T10:00:00.000Z", "updatedAt": "2026-09-20T15:30:00.000Z"}}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/DocButterflyReadTemplate" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"connectionId": "conn_0123456789abcdef0123456789abcdef", "templateId": "3f2b8c1e-5d4a-4f6b-9c0d-1e2f3a4b5c6d"}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""connectionId"": ""conn_0123456789abcdef0123456789abcdef"", ""templateId"": ""3f2b8c1e-5d4a-4f6b-9c0d-1e2f3a4b5c6d""}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/DocButterflyReadTemplate", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/DocButterflyReadTemplate"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"connectionId": "conn_0123456789abcdef0123456789abcdef", "templateId": "3f2b8c1e-5d4a-4f6b-9c0d-1e2f3a4b5c6d"}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/DocButterflyReadTemplate
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "connectionId": "conn_0123456789abcdef0123456789abcdef",
│      "templateId": "3f2b8c1e-5d4a-4f6b-9c0d-1e2f3a4b5c6d"
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/DocButterflyReadTemplate"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

DocButterfly: Read Vault Document

POST /api/DocButterflyReadVaultDocument 1 token

Reads one document out of your own DocButterfly vault, from its non-lockbox side, through a connection of type 'dbfClient' (DocButterfly, this account). The newest version unless you name one, as the original file's bytes.

THE LOCKBOX SIDE IS REFUSED, AND SAYS WHY. It is sealed under the certificate on your authorized devices, so DocButterfly cannot open it and no workflow can read it: a request for it answers 403 VAULT_LOCKBOX_SEALED before anything is read. Only documents filed for everyone in the account are readable this way; one filed for HR, a manager or a named person answers 404 VAULT_DOCUMENT_NOT_FOUND, exactly like one that does not exist. THIS ACCOUNT ONLY: a connection set to another account or another address is refused by name. Files up to 32 MB, the same budget BlobReadFile enforces, checked from the stored size before a byte moves and again on what arrived. Reading is not a side effect. Uses a saved connection, so it is not available in the anonymous playground or the interactive builder Run.
Parameters
NameTypeRequiredDescription
connectionId string required Registry connection id (conn_…) of type 'dbfClient', owned by the calling client (manage/connections)
documentId integer required The vault document's id, a positive whole number
version integer optional Which version to read. Leave it out for the newest
side string optional Which side of the vault to read. Only the non-lockbox side can be read by a workflow; 'lockbox' is refused with 403 VAULT_LOCKBOX_SEALED (default: nonLockbox) (one of: nonLockbox, lockbox)
Request Example
JSON
{"connectionId": "conn_0123456789abcdef0123456789abcdef", "documentId": 42}
Response Example
JSON
{"success": true, "connectionId": "conn_0123456789abcdef0123456789abcdef", "side": "nonLockbox", "documentId": 42, "version": 2, "fileName": "lease-2026.pdf", "mediaType": "application/pdf", "sizeBytes": 20481, "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "sensitivity": "normal", "createdAt": "2026-09-20T15:30:00.000Z", "contentBase64": "JVBERi0xLjcK..."}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/DocButterflyReadVaultDocument" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"connectionId": "conn_0123456789abcdef0123456789abcdef", "documentId": 42}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""connectionId"": ""conn_0123456789abcdef0123456789abcdef"", ""documentId"": 42}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/DocButterflyReadVaultDocument", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/DocButterflyReadVaultDocument"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"connectionId": "conn_0123456789abcdef0123456789abcdef", "documentId": 42}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/DocButterflyReadVaultDocument
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "connectionId": "conn_0123456789abcdef0123456789abcdef",
│      "documentId": 42
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/DocButterflyReadVaultDocument"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed

Connection: Test

POST /api/ConnectionTest 1 token

Answers 'does this saved connection actually work?' for every registry connection type — dataverse, rest, sqlserver, postgresql, mysql, sftp, blob and servicebus — with one request shape. Each type has its own read-only probe: a WhoAmI call, a GET of the base URL, SELECT 1, an stat of the SFTP root, getProperties on the container, or a GET of the Service Bus entity description.

EVERY PROBE IS READ-ONLY. Nothing uploads, sends, creates or deletes: a connection test that writes is one nobody dares run twice, and 'test it' must never become a way to litter a customer's server, container or queue. A FAILING PROBE IS A SUCCESSFUL TEST — a connection that cannot authenticate as stored answers 200 with ok:false and the reason, because that is the answer you asked for. The three-way split is deliberate: 400/404/401 means the REQUEST is wrong (bad body, unknown or foreign connection, bare admin key), 502 means OUR infrastructure could not produce the credential and nothing was probed, and 200 means the connection was tested. This endpoint is purely diagnostic: there is no write path in it at all, so it never marks, mutates or annotates the connection. A connection type with no probe yet answers ok:false with 'UNSUPPORTED type for testing (yet)' — a clean answer, not an error. Reading is not a side effect.
Parameters
NameTypeRequiredDescription
connectionId string required Registry connection id (conn_…) of any type, owned by the calling client (manage/connections)
Request Example
JSON
{"connectionId": "conn_0123456789abcdef0123456789abcdef"}
Response Example
JSON
{"success": true, "type": "sqlserver", "ok": true, "latencyMs": 214, "detail": "SELECT 1 succeeded"}
Code Examples
curl -X POST "https://api.docbutterfly.com/api/ConnectionTest" \
  -H "X-API-Key: df_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"connectionId": "conn_0123456789abcdef0123456789abcdef"}'
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "df_your_api_key_here");

var json = @"{""connectionId"": ""conn_0123456789abcdef0123456789abcdef""}";
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");

var response = await client.PostAsync("https://api.docbutterfly.com/api/ConnectionTest", content);
response.EnsureSuccessStatusCode();

var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
import requests
import json

url = "https://api.docbutterfly.com/api/ConnectionTest"
headers = {
    "X-API-Key": "df_your_api_key_here",
    "Content-Type": "application/json"
}
payload = json.loads('{"connectionId": "conn_0123456789abcdef0123456789abcdef"}')

response = requests.post(url, headers=headers, json=payload)
response.raise_for_status()

data = response.json()
print(json.dumps(data, indent=2))
┌─────────────────────────────────────────────┐
│  Power Automate - HTTP Action               │
├─────────────────────────────────────────────┤
│                                             │
│  Method:  POST                              │
│  URI:     https://api.docbutterfly.com/api/ConnectionTest
│                                             │
│  Headers:                                   │
│    X-API-Key:    df_your_api_key_here       │
│    Content-Type: application/json           │
│                                             │
│  Body:                                      │
│    {
│      "connectionId": "conn_0123456789abcdef0123456789abcdef"
│    }
│                                             │
└─────────────────────────────────────────────┘

Steps:
1. Add an HTTP action to your flow
2. Set Method to "POST"
3. Set URI to "https://api.docbutterfly.com/api/ConnectionTest"
4. Add the headers shown above
5. Paste the Body JSON into the Body field
6. Replace placeholder values with dynamic content as needed
Try in Testbed