API Reference

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.com

For local development: http://localhost:3000

Authentication

All requests require Bearer token authentication. Include your API key in the Authorization header.

Authorization: Bearer YOUR_API_KEY

Create 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.

POST/api/v1/plans

Request Body

FieldTypeRequiredDescription
domainstringYesThe website domain (e.g., "kohls.com")
taskstringYesNatural language description of what to find
plan_typesstring[]NoWhich plan types to include. Defaults to ["deep_links", "guidance"]
debug_levelstringNoSet to "verbose" to include debug logs

Plan Types

TypeDescription
deep_linksUses indexed search engines to generate a deep-linked URL with filters, sorts, and visual confirmation guidance applied
guidanceSearches for relevant help articles and generates step-by-step guidance for the agent
refinementsComing 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

FieldTypeDescription
urlstringThe constructed URL with filters/sorts applied
descriptionstringHuman-readable description of the URL contents
plan_typesstring[]Which plan types were requested
url_linking_supportedbooleanfalse when manual navigation is required
alternativesarrayAlternative URLs with fewer filters as fallbacks
supplemental_strategiesarrayAdditional strategy results (e.g., web search alongside search)
guidanceobjectNavigation guidance for browser agents
refinementsobjectPlaceholder for upcoming refinements feature
debug_logarrayDebug 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

GET/api/v1/domains?domain=example.com
ParameterTypeRequiredDescription
domainstringYesThe domain to check (query parameter)

Status Values

StatusDescription
indexedProcessing complete with at least one working search engine
indexingCurrently being processed
queuedPending processing
errorProcessing failed or all engines errored
noneNot 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

POST/api/v1/domains
FieldTypeRequiredDescription
domainstringYes*The domain to index (e.g., "kohls.com")
urlstringYes*Full URL to index (e.g., "https://www.kohls.com")
forcebooleanNoForce 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

StatusErrorDescription
400Missing or invalid fieldRequired request body fields are missing or invalid
401UnauthorizedMissing, invalid, or expired API key
404Not foundSite not indexed, or no strategy found for the task
500Internal server errorServer-side error (details in development only)

Best Practices

Writing Good Task Descriptions

GoodBad
"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

  1. Check status first with GET /api/v1/domains before creating plans.
  2. Queue if needed with POST /api/v1/domains for unindexed domains.
  3. Poll for completion — indexing typically takes a few minutes.
  4. Create plans once the domain status is "indexed".

Handling Responses

  1. Check url_linking_supported — if false, use guidance.navigation instructions instead of the URL.
  2. Use alternatives as fallbacks if the primary URL doesn't load expected results.
  3. Display guidance to 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;
}