Tilt API
The Tilt API helps browser agents navigate websites by converting natural language tasks into actionable plans — deep-linked URLs with filters and sorts applied, guidance from help articles, and more.
Base URL
https://api.heytilt.comFor local development: http://localhost:3000
Authentication
All requests require Bearer token authentication. Include your API key in the Authorization header.
Authorization: Bearer YOUR_API_KEYCreate API keys from your dashboard. Keys are scoped to your user account.
Plans API
The Plans API is the primary endpoint for browser agents. Given a domain and a task, it crafts a plan: the fastest, most reliable path to the correct UI state.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
domain | string | Yes | The website domain (e.g., "kohls.com") |
task | string | Yes | Natural language description of what to find |
plan_types | string[] | No | Which plan types to include. Defaults to ["deep_links", "guidance"] |
debug_level | string | No | Set to "verbose" to include debug logs |
Plan Types
| Type | Description |
|---|---|
deep_links | Uses indexed search engines to generate a deep-linked URL with filters, sorts, and visual confirmation guidance applied |
guidance | Searches for relevant help articles and generates step-by-step guidance for the agent |
refinements | Coming soon. Will return selectors for fetching results and AI-powered refinements |
Examples
cURL
curl -X POST https://api.heytilt.com/api/v1/plans \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"domain": "kohls.com",
"task": "black t-shirts in medium size sorted by price low to high"
}'cURL with plan_types
curl -X POST https://api.heytilt.com/api/v1/plans \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"domain": "kohls.com",
"task": "black t-shirts in medium size",
"plan_types": ["deep_links"]
}'JavaScript / TypeScript
const response = await fetch("https://api.heytilt.com/api/v1/plans", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${API_KEY}`,
},
body: JSON.stringify({
domain: "kohls.com",
task: "black t-shirts in medium size sorted by price low to high",
}),
});
const plan = await response.json();
console.log(plan.url);Python
import requests
response = requests.post(
"https://api.heytilt.com/api/v1/plans",
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {API_KEY}",
},
json={
"domain": "kohls.com",
"task": "black t-shirts in medium size sorted by price low to high",
},
)
plan = response.json()
print(plan["url"])Response
A successful response contains the plan URL, description, guidance, and any alternatives or supplemental results.
Example Response
{
"url": "https://www.kohls.com/catalog/mens-t-shirts.jsp?CN=...",
"description": "Search for 't-shirts' filtered by Color: Black, Size: Medium",
"plan_types": ["deep_links", "guidance"],
"alternatives": [
{
"url": "https://www.kohls.com/catalog/...",
"description": "Without size filter"
}
],
"guidance": {
"site": "Kohls uses faceted navigation.",
"filter_visual_confirmation": "Selected filters appear as chips above the grid",
"sort_visual_confirmation": "Current sort is highlighted in the dropdown"
}
}Manual Navigation
When url_linking_supported is false, the site doesn't support deep-linking. Use the guidance.navigation instructions instead.
{
"url": "https://www.example.com/mortgage-calculator",
"description": "Navigate to mortgage calculator and follow guidance",
"url_linking_supported": false,
"plan_types": ["deep_links"],
"guidance": {
"navigation": "1. Enter loan amount...\n2. Set interest rate...",
"site": "Calculator inputs are client-side only"
}
}Response Fields
| Field | Type | Description |
|---|---|---|
url | string | The constructed URL with filters/sorts applied |
description | string | Human-readable description of the URL contents |
plan_types | string[] | Which plan types were requested |
url_linking_supported | boolean | false when manual navigation is required |
alternatives | array | Alternative URLs with fewer filters as fallbacks |
supplemental_strategies | array | Additional strategy results (e.g., web search alongside search) |
guidance | object | Navigation guidance for browser agents |
refinements | object | Placeholder for upcoming refinements feature |
debug_log | array | Debug information (only with debug_level: "verbose") |
Domains API
The Domains API provides access to domain indexing status and allows queuing new domains for processing.
Check Indexing Status
| Parameter | Type | Required | Description |
|---|---|---|---|
domain | string | Yes | The domain to check (query parameter) |
Status Values
| Status | Description |
|---|---|
indexed | Processing complete with at least one working search engine |
indexing | Currently being processed |
queued | Pending processing |
error | Processing failed or all engines errored |
none | Not in the system |
curl -X GET "https://api.heytilt.com/api/v1/domains?domain=kohls.com" \
-H "Authorization: Bearer YOUR_API_KEY"{
"domain": "kohls.com",
"status": "indexed",
"site_name": "Kohl's",
"search_engines": [
{ "id": 1, "name": "Product Search", "status": "complete" },
{ "id": 2, "name": "Store Locator", "status": "error" }
]
}Queue Domain for Indexing
| Field | Type | Required | Description |
|---|---|---|---|
domain | string | Yes* | The domain to index (e.g., "kohls.com") |
url | string | Yes* | Full URL to index (e.g., "https://www.kohls.com") |
force | boolean | No | Force re-indexing even if already indexed or in progress |
* Provide either domain or url (at least one is required).
curl -X POST https://api.heytilt.com/api/v1/domains \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{"domain": "kohls.com"}'Errors
| Status | Error | Description |
|---|---|---|
400 | Missing or invalid field | Required request body fields are missing or invalid |
401 | Unauthorized | Missing, invalid, or expired API key |
404 | Not found | Site not indexed, or no strategy found for the task |
500 | Internal server error | Server-side error (details in development only) |
Best Practices
Writing Good Task Descriptions
| Good | Bad |
|---|---|
"black nike running shoes size 10 for men" | "shoes" |
"laptop under $1000 with 16GB RAM" | "cheap laptop" |
"organic coffee beans sorted by customer rating" | "coffee" |
Domain Workflow
- Check status first with
GET /api/v1/domainsbefore creating plans. - Queue if needed with
POST /api/v1/domainsfor unindexed domains. - Poll for completion — indexing typically takes a few minutes.
- Create plans once the domain status is
"indexed".
Handling Responses
- Check
url_linking_supported— iffalse, useguidance.navigationinstructions instead of the URL. - Use
alternativesas fallbacks if the primary URL doesn't load expected results. - Display
guidanceto help the agent understand the navigation context.
TypeScript Types
type PlanType = "deep_links" | "guidance" | "refinements";
interface PlanRequest {
domain: string;
task: string;
plan_types?: PlanType[];
debug_level?: "verbose";
}
interface PlanResponse {
url: string;
description: string;
url_linking_supported?: boolean;
plan_types: PlanType[];
alternatives?: Array<{ url: string; description: string }>;
supplemental_strategies?: Array<{
url?: string;
description: string;
results?: Array<{ url: string; title: string; description: string; commentary?: string }>;
}>;
guidance?: {
site?: string;
selection?: string;
search_engine?: string;
navigation?: string;
task_specific?: string;
filter_visual_confirmation?: string;
sort_visual_confirmation?: string;
};
refinements?: {
status: "coming_soon";
description: string;
};
debug_log?: Array<{ step: string; timestamp: string; details?: any; error?: string }>;
}
type IndexingStatus = "indexed" | "error" | "indexing" | "queued" | "none";
interface DomainStatusResponse {
domain: string;
status: IndexingStatus;
site_name?: string | null;
search_engines?: Array<{ id: number; name: string | null; status: string | null }>;
}
interface DomainQueueRequest {
domain?: string; url?: string; force?: boolean;
}
interface DomainQueueResponse {
domain: string; status: IndexingStatus; message: string;
}
interface ApiError {
error: string; details?: string;
}