Connected apps
List an app's tools
Everything an app can do, with each tool’s inputs and kind.
GET
/
v1
/
apps
/
{app}
/
tools
List an app's tools
curl --request GET \
--url https://api.valendata.com/v1/apps/{app}/tools \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.valendata.com/v1/apps/{app}/tools"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.valendata.com/v1/apps/{app}/tools', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.valendata.com/v1/apps/{app}/tools",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.valendata.com/v1/apps/{app}/tools"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.valendata.com/v1/apps/{app}/tools")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.valendata.com/v1/apps/{app}/tools")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"tools": [
{
"slug": "GMAIL_FETCH_EMAILS",
"name": "Fetch emails",
"description": "Fetches emails from the mailbox, filtered by a Gmail search query.",
"app": "gmail",
"kind": "read",
"inputs": [
{
"name": "query",
"type": "string",
"description": "Gmail search query, e.g. is:unread from:boss@example.com.",
"required": false
},
{
"name": "max_results",
"type": "integer",
"description": "Most emails to return.",
"required": false
}
]
}
],
"plan": [
"Fetch with query 'is:unread from:<boss address>'."
],
"pitfalls": [
"Without a query, the newest emails of every sender come back."
],
"next_cursor": null,
"has_more": false
}{
"detail": "The request is malformed: the body is not JSON, the cursor or Idempotency-Key is not valid.",
"code": "invalid_json",
"type": "invalid_request_error",
"request_id": "3f9a1c2b7d4e"
}{
"detail": "The API key is missing, malformed, revoked, or expired.",
"code": "unauthorized",
"type": "authentication_error",
"request_id": "3f9a1c2b7d4e"
}{
"detail": "The key lacks a scope, is limited to other skills or workflows, or you may not do this.",
"code": "permission_denied",
"type": "permission_error",
"request_id": "3f9a1c2b7d4e"
}{
"detail": "It does not exist, or it is not yours to see.",
"code": "not_found",
"type": "invalid_request_error",
"request_id": "3f9a1c2b7d4e"
}{
"detail": "Invalid request — name: Field required",
"code": "validation_error",
"type": "invalid_request_error",
"request_id": "3f9a1c2b7d4e",
"param": "name",
"errors": [
{
"type": "missing",
"loc": [
"body",
"name"
],
"msg": "Field required"
}
]
}{
"detail": "Too many requests for this key, or too many runs in progress. Wait `Retry-After` seconds.",
"code": "rate_limited",
"type": "rate_limit_error",
"request_id": "3f9a1c2b7d4e"
}{
"detail": "Something broke on our side. Retry; runs that fail on our side are refunded.",
"code": "internal_error",
"type": "api_error",
"request_id": "3f9a1c2b7d4e"
}{
"detail": "A connected app, or the service that connects it, answered with an error or could not be reached.",
"code": "upstream_error",
"type": "api_error",
"request_id": "3f9a1c2b7d4e"
}{
"detail": "This part of the API is not available on the server right now. Retry later.",
"code": "service_unavailable",
"type": "api_error",
"request_id": "3f9a1c2b7d4e"
}Without
q, every tool of the app, most used first. Some apps have hundreds; page with limit and cursor.
With q, a task in plain words such as unread emails from my boss, you get only the few tools that do it, best first, with plan (steps that usually do the task) and pitfalls. This answer is one page.
Each tool has its inputs (name, type, description, required) and its kind: read or write. See Read and write. Run one with Run an app tool.
Needs apps:read. MCP tool: list_app_tools.Authorizations
ApiKeyAuthApiKeyHeader
API key with the vd_sk_ prefix, as Authorization: Bearer vd_sk_.... Create keys in Settings → API Keys.
Path Parameters
Query Parameters
A task in plain words: only the tools that do it, best first, with a plan (no paging).
Maximum string length:
300Items per page, 1–100.
Required range:
1 <= x <= 100next_cursor of the previous page.
Response
The app's tools.
Show child attributes
Show child attributes
True when there is a next page (next_cursor is not null).
Pass as ?cursor= for the next page; null on the last page.
With q: steps that usually do the task.
With q: mistakes to avoid.
⌘I
List an app's tools
curl --request GET \
--url https://api.valendata.com/v1/apps/{app}/tools \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.valendata.com/v1/apps/{app}/tools"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.valendata.com/v1/apps/{app}/tools', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.valendata.com/v1/apps/{app}/tools",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.valendata.com/v1/apps/{app}/tools"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.valendata.com/v1/apps/{app}/tools")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.valendata.com/v1/apps/{app}/tools")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"tools": [
{
"slug": "GMAIL_FETCH_EMAILS",
"name": "Fetch emails",
"description": "Fetches emails from the mailbox, filtered by a Gmail search query.",
"app": "gmail",
"kind": "read",
"inputs": [
{
"name": "query",
"type": "string",
"description": "Gmail search query, e.g. is:unread from:boss@example.com.",
"required": false
},
{
"name": "max_results",
"type": "integer",
"description": "Most emails to return.",
"required": false
}
]
}
],
"plan": [
"Fetch with query 'is:unread from:<boss address>'."
],
"pitfalls": [
"Without a query, the newest emails of every sender come back."
],
"next_cursor": null,
"has_more": false
}{
"detail": "The request is malformed: the body is not JSON, the cursor or Idempotency-Key is not valid.",
"code": "invalid_json",
"type": "invalid_request_error",
"request_id": "3f9a1c2b7d4e"
}{
"detail": "The API key is missing, malformed, revoked, or expired.",
"code": "unauthorized",
"type": "authentication_error",
"request_id": "3f9a1c2b7d4e"
}{
"detail": "The key lacks a scope, is limited to other skills or workflows, or you may not do this.",
"code": "permission_denied",
"type": "permission_error",
"request_id": "3f9a1c2b7d4e"
}{
"detail": "It does not exist, or it is not yours to see.",
"code": "not_found",
"type": "invalid_request_error",
"request_id": "3f9a1c2b7d4e"
}{
"detail": "Invalid request — name: Field required",
"code": "validation_error",
"type": "invalid_request_error",
"request_id": "3f9a1c2b7d4e",
"param": "name",
"errors": [
{
"type": "missing",
"loc": [
"body",
"name"
],
"msg": "Field required"
}
]
}{
"detail": "Too many requests for this key, or too many runs in progress. Wait `Retry-After` seconds.",
"code": "rate_limited",
"type": "rate_limit_error",
"request_id": "3f9a1c2b7d4e"
}{
"detail": "Something broke on our side. Retry; runs that fail on our side are refunded.",
"code": "internal_error",
"type": "api_error",
"request_id": "3f9a1c2b7d4e"
}{
"detail": "A connected app, or the service that connects it, answered with an error or could not be reached.",
"code": "upstream_error",
"type": "api_error",
"request_id": "3f9a1c2b7d4e"
}{
"detail": "This part of the API is not available on the server right now. Retry later.",
"code": "service_unavailable",
"type": "api_error",
"request_id": "3f9a1c2b7d4e"
}
