Search
curl --request POST \
--url https://index.trygravity.com/search \
--header 'Content-Type: application/json' \
--data '
{
"query": "<string>",
"search_id": "<string>",
"context": {},
"platform_api_key": "<string>",
"external_session_id": "<string>",
"external_user_id_hash": "<string>",
"external_user_id": "<string>",
"hashed_email": "<string>",
"metadata": {},
"monetization": {}
}
'import requests
url = "https://index.trygravity.com/search"
payload = {
"query": "<string>",
"search_id": "<string>",
"context": {},
"platform_api_key": "<string>",
"external_session_id": "<string>",
"external_user_id_hash": "<string>",
"external_user_id": "<string>",
"hashed_email": "<string>",
"metadata": {},
"monetization": {}
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
query: '<string>',
search_id: '<string>',
context: {},
platform_api_key: '<string>',
external_session_id: '<string>',
external_user_id_hash: '<string>',
external_user_id: '<string>',
hashed_email: '<string>',
metadata: {},
monetization: {}
})
};
fetch('https://index.trygravity.com/search', 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://index.trygravity.com/search",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'query' => '<string>',
'search_id' => '<string>',
'context' => [
],
'platform_api_key' => '<string>',
'external_session_id' => '<string>',
'external_user_id_hash' => '<string>',
'external_user_id' => '<string>',
'hashed_email' => '<string>',
'metadata' => [
],
'monetization' => [
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://index.trygravity.com/search"
payload := strings.NewReader("{\n \"query\": \"<string>\",\n \"search_id\": \"<string>\",\n \"context\": {},\n \"platform_api_key\": \"<string>\",\n \"external_session_id\": \"<string>\",\n \"external_user_id_hash\": \"<string>\",\n \"external_user_id\": \"<string>\",\n \"hashed_email\": \"<string>\",\n \"metadata\": {},\n \"monetization\": {}\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://index.trygravity.com/search")
.header("Content-Type", "application/json")
.body("{\n \"query\": \"<string>\",\n \"search_id\": \"<string>\",\n \"context\": {},\n \"platform_api_key\": \"<string>\",\n \"external_session_id\": \"<string>\",\n \"external_user_id_hash\": \"<string>\",\n \"external_user_id\": \"<string>\",\n \"hashed_email\": \"<string>\",\n \"metadata\": {},\n \"monetization\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://index.trygravity.com/search")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"query\": \"<string>\",\n \"search_id\": \"<string>\",\n \"context\": {},\n \"platform_api_key\": \"<string>\",\n \"external_session_id\": \"<string>\",\n \"external_user_id_hash\": \"<string>\",\n \"external_user_id\": \"<string>\",\n \"hashed_email\": \"<string>\",\n \"metadata\": {},\n \"monetization\": {}\n}"
response = http.request(request)
puts response.read_body{
"search_id": "<string>",
"recommendation": {
"name": "<string>",
"slug": "<string>",
"category": "<string>",
"description": "<string>",
"website_url": "<string>",
"docs_url": "<string>"
},
"reasoning": "<string>",
"install": {
"summary": "<string>",
"steps": [
{}
],
"env_vars": [
{}
],
"notes": "<string>"
},
"credential_request": {
"setup_url": "<string>",
"required_env_vars": [
{}
],
"user_message": "<string>",
"agent_instruction": "<string>",
"grclid": "<string>"
},
"click_url": "<string>",
"grclid": "<string>"
}Interfaces
Search
Find the best tool for a development task
POST
/
search
Search
curl --request POST \
--url https://index.trygravity.com/search \
--header 'Content-Type: application/json' \
--data '
{
"query": "<string>",
"search_id": "<string>",
"context": {},
"platform_api_key": "<string>",
"external_session_id": "<string>",
"external_user_id_hash": "<string>",
"external_user_id": "<string>",
"hashed_email": "<string>",
"metadata": {},
"monetization": {}
}
'import requests
url = "https://index.trygravity.com/search"
payload = {
"query": "<string>",
"search_id": "<string>",
"context": {},
"platform_api_key": "<string>",
"external_session_id": "<string>",
"external_user_id_hash": "<string>",
"external_user_id": "<string>",
"hashed_email": "<string>",
"metadata": {},
"monetization": {}
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
query: '<string>',
search_id: '<string>',
context: {},
platform_api_key: '<string>',
external_session_id: '<string>',
external_user_id_hash: '<string>',
external_user_id: '<string>',
hashed_email: '<string>',
metadata: {},
monetization: {}
})
};
fetch('https://index.trygravity.com/search', 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://index.trygravity.com/search",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'query' => '<string>',
'search_id' => '<string>',
'context' => [
],
'platform_api_key' => '<string>',
'external_session_id' => '<string>',
'external_user_id_hash' => '<string>',
'external_user_id' => '<string>',
'hashed_email' => '<string>',
'metadata' => [
],
'monetization' => [
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://index.trygravity.com/search"
payload := strings.NewReader("{\n \"query\": \"<string>\",\n \"search_id\": \"<string>\",\n \"context\": {},\n \"platform_api_key\": \"<string>\",\n \"external_session_id\": \"<string>\",\n \"external_user_id_hash\": \"<string>\",\n \"external_user_id\": \"<string>\",\n \"hashed_email\": \"<string>\",\n \"metadata\": {},\n \"monetization\": {}\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://index.trygravity.com/search")
.header("Content-Type", "application/json")
.body("{\n \"query\": \"<string>\",\n \"search_id\": \"<string>\",\n \"context\": {},\n \"platform_api_key\": \"<string>\",\n \"external_session_id\": \"<string>\",\n \"external_user_id_hash\": \"<string>\",\n \"external_user_id\": \"<string>\",\n \"hashed_email\": \"<string>\",\n \"metadata\": {},\n \"monetization\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://index.trygravity.com/search")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"query\": \"<string>\",\n \"search_id\": \"<string>\",\n \"context\": {},\n \"platform_api_key\": \"<string>\",\n \"external_session_id\": \"<string>\",\n \"external_user_id_hash\": \"<string>\",\n \"external_user_id\": \"<string>\",\n \"hashed_email\": \"<string>\",\n \"metadata\": {},\n \"monetization\": {}\n}"
response = http.request(request)
puts response.read_body{
"search_id": "<string>",
"recommendation": {
"name": "<string>",
"slug": "<string>",
"category": "<string>",
"description": "<string>",
"website_url": "<string>",
"docs_url": "<string>"
},
"reasoning": "<string>",
"install": {
"summary": "<string>",
"steps": [
{}
],
"env_vars": [
{}
],
"notes": "<string>"
},
"credential_request": {
"setup_url": "<string>",
"required_env_vars": [
{}
],
"user_message": "<string>",
"agent_instruction": "<string>",
"grclid": "<string>"
},
"click_url": "<string>",
"grclid": "<string>"
}The core endpoint for developer services. An AI reasoning agent analyzes your query against the catalog and returns the single best match with an explanation of why it fits.
Every matched response includes the reasoning and a tracked click link (when the recommended service has a destination URL) that the agent can hand to the user. Pass your
The Index remembers the previous conversation and answers in context.
external_session_id so the Index can use the conversation as private supporting context; see Conversation context.
Looking for physical products (shoes, cookware, electronics)? That is a different vertical with a
ranked-list response; use
POST /shop/search. Sending a shopping
query here returns no match.Authentication
Send your publisher key as theX-API-Key header or as the platform_api_key body field. One of the
two is required.
string
Publisher API key. Alternative to the
platform_api_key body field.Request
string
required
What you need. Natural language — e.g. “serverless database with free tier” or “auth for Next.js with SSO”
string
Pass a
search_id from a previous response to ask a follow-up question. The Index maintains conversation context.object
Additional project context the reasoning agent should weigh, e.g.
{"framework": "nextjs", "language": "typescript", "constraints": ["free tier", "EU region"]}. Free-form; anything the
user already told you about their stack belongs here rather than stuffed into query.string
Publisher API key for attribution. Alternative to the
X-API-Key header — send one or the other.string
Your stable chat/session ID. Send it by default: when Gravity has a summary of that conversation,
Index uses it as private supporting context for the recommendation (see
Conversation context). Also used for attribution and payout
matching. Never returned in the response.
string
Hash of your user ID. Prefer this over sending a raw user identifier.
string
Optional raw user ID. Gravity stores only a SHA-256 hash.
string
SHA-256 of the user’s normalized email (
email.strip().lower()), when available. This is the canonical field and matches the ad request convention. The legacy email_hash field is still accepted as an alias.object
Non-sensitive publisher metadata such as surface, workspace, plan, or placement. Stored with the search for attribution/debugging; not used in the LLM prompt.
object
Optional private publisher ranking controls. Requires
platform_api_key. Supported mode: boost_cpa — relevance-first ranking that gives a small boost to services with an active CPA campaign. Omit (or use off) for pure relevance. Economics are not returned in the response.Response
string
Unique ID for this search. Pass it back for follow-ups.
object
string
Why this tool is the best fit for your specific query. Natural language explanation.
object
Optional structured setup details for the recommended service. Present when the catalog has them; agents may ignore this and simply present the recommendation.
object
Explicit handoff instructions for credentials the human user must fetch.
string
Tracked short link the user visits to sign up with the recommended service.
string
Gravity Index click ID minted for the tracked click link.
Examples
Basic search
curl -X POST https://index.trygravity.com/search \
-H "Content-Type: application/json" \
-H "X-API-Key: $GRAVITY_PUBLISHER_KEY" \
-d '{
"query": "I need a serverless database with a free tier",
"external_session_id": "session_123",
"external_user_id_hash": "sha256_user_id",
"hashed_email": "sha256_email",
"metadata": {"surface": "chat"}
}'
{
"search_id": "238c2e03-347b-438e-8b52-f9cacee31ebe",
"recommendation": {
"name": "Neon",
"slug": "neon",
"category": "Database",
"description": "Serverless Postgres with branching, autoscaling, and bottomless storage.",
"website_url": "https://neon.tech"
},
"reasoning": "Neon is the best fit because it's serverless Postgres with a generous free tier, supports edge functions, and has database branching for preview deployments.",
"install": {
"summary": "Provision a serverless Postgres database",
"steps": [
{"step": 1, "action": "Install the Neon driver", "command": "npm install @neondatabase/serverless"},
{"step": 2, "action": "Create a db client", "file": "lib/db.ts"},
{"step": 3, "action": "Add connection string", "file": ".env.local"},
{"step": 4, "action": "Get credentials", "user_action": "Go to https://console.neon.tech → Connection Details"}
],
"env_vars": ["DATABASE_URL"]
},
"credential_request": {
"setup_url": "https://index.trygravity.ai/go/xKHR2xqP9z",
"required_env_vars": ["DATABASE_URL"],
"user_message": "Open the setup link for Neon, create or find the required credentials, then paste the values for `DATABASE_URL` back into the agent so it can finish the integration.",
"agent_instruction": "Pause for any missing credentials, ask the user to open setup_url, then continue once they paste values back.",
"grclid": "xKHR2xqP9z"
},
"click_url": "https://index.trygravity.ai/go/xKHR2xqP9z",
"grclid": "xKHR2xqP9z"
}
Follow-up question
curl -X POST https://index.trygravity.com/search \
-H "Content-Type: application/json" \
-H "X-API-Key: $GRAVITY_PUBLISHER_KEY" \
-d '{
"search_id": "238c2e03-347b-438e-8b52-f9cacee31ebe",
"query": "Does it support database branching for preview deploys?"
}'

