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

23 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. The discovery half of the SQL source, and what the Template Generator's Connections flow builds its table picker 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. 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}]}], "truncated": false}
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)
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

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)
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)
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. 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, "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. 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, "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

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