Documentation Index Fetch the complete documentation index at: https://ulpi.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
Build Documentation Search Into Your Applications
Your team needs documentation search in places ULPI doesnβt reach:
Internal documentation portals
Slack bots for instant doc lookups
IDE extensions
CLI tools
Admin dashboards
Custom AI agents
The ULPI REST API lets you integrate semantic documentation search anywhere.
This guide shows you:
π How to authenticate and make your first API call (2 minutes)
π‘ Real-world integration examples (Slack bots, portals, CLI tools)
π οΈ Production-ready code in JavaScript, Python, PHP, Go
β‘ Best practices for caching, rate limits, and error handling
π Performance optimization strategies
Just want to use ULPI with Claude/Cursor? See Getting Started for MCP setup instead.Building something custom? This guide is for you.
Why Use the API?
The MCP server is great for AI assistants, but the API unlocks custom integrations:
Custom Search UIs Build internal documentation portals:
Company-branded search interface
Advanced filtering and faceting
Usage analytics and tracking
Integration with SSO/auth
Example: Acme Corpβs internal dev portal with ULPI search
Slack/Discord Bots Instant doc lookups in chat:
/docs deploy to production β Get deployment guide
No context switching
Share results with team
Track common questions
Example: Engineering Slack bot with 500+ daily searches
CLI Tools Terminal-based documentation search:
docs auth β Find auth docs
Integrate with shell scripts
CI/CD pipeline helpers
Developer productivity boost
Example: company-docs CLI tool for all repos
Custom AI Agents Build specialized AI assistants:
Customer support chatbots
Onboarding assistants
Technical troubleshooting bots
Integration with your AI stack
Example: Support bot that searches internal docs first
Plus: Dashboards, IDE plugins, mobile apps, workflow automation, and more.
Quick Start: Your First API Call
Get up and running in 2 minutes:
Get Your API Key
Generate an API key:
Go to app.ulpi.io/api-keys
Click Create API Key
Name: βAPI Integration Testβ
Environment: live (production)
Copy the key (starts with ulpi_live_sk_...)
Save your API key immediately! Itβs only shown once.Store in password manager or environment variable.
Make Your First Request
Test with cURL: curl -X POST https://api.ulpi.io/api/v1/documentation/search \
-H "Authorization: Bearer ulpi_live_YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"query": "How do I deploy to production?",
"limit": 3
}'
Replace ulpi_live_YOUR_KEY_HERE with your actual API key.
View Results
Youβll get JSON response: {
"results" : [
{
"content" : "To deploy to production: \n\n 1. Run tests..." ,
"file" : "docs/deployment.md" ,
"repository" : "backend-api" ,
"branch" : "main" ,
"url" : "https://github.com/org/repo/blob/main/docs/deployment.md#L42" ,
"score" : 0.94 ,
"last_updated" : "2025-01-10T14:23:00Z"
}
],
"meta" : {
"total" : 12 ,
"query_time_ms" : 45 ,
"cached" : false
}
}
Success! You just searched your documentation via API.
Total time: 2 minutes from API key to first results
Real-World Integration Examples
See how teams use the ULPI API:
π€ Slack Bot for Documentation Lookup
Use case: Engineers ask /docs <question> in Slack, bot returns relevant docsBenefits:
No context switching (stay in Slack)
Share results with team instantly
Track most-asked questions
Reduce Slack interruptions (βWhereβs the deploy doc?β)
Implementation: Bolt.js (JavaScript)
Python (slack_sdk)
// slack-bot.js
const { App } = require ( '@slack/bolt' );
const app = new App ({
token: process . env . SLACK_BOT_TOKEN ,
signingSecret: process . env . SLACK_SIGNING_SECRET
});
// /docs command
app . command ( '/docs' , async ({ command , ack , respond }) => {
await ack ();
// Search ULPI
const results = await fetch ( 'https://api.ulpi.io/api/v1/documentation/search' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ process . env . ULPI_API_KEY } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
query: command . text ,
limit: 3
})
}). then ( r => r . json ());
// Format for Slack
await respond ({
text: `Found ${ results . meta . total } results for " ${ command . text } ":` ,
blocks: results . results . map ( r => ({
type: 'section' ,
text: {
type: 'mrkdwn' ,
text: `* ${ r . file } * ( ${ r . repository } ) \n ${ r . content . substring ( 0 , 300 ) } ... \n < ${ r . url } |View on GitHub>`
}
}))
});
});
app . start ( 3000 );
Usage in Slack: /docs how to deploy to production
/docs authentication with JWT
/docs troubleshoot 500 errors
# slack_bot.py
from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler
import requests
import os
app = App( token = os.environ[ "SLACK_BOT_TOKEN" ])
@app.command ( "/docs" )
def handle_docs_command ( ack , command , respond ):
ack()
query = command[ 'text' ]
# Search ULPI
response = requests.post(
'https://api.ulpi.io/api/v1/documentation/search' ,
headers = {
'Authorization' : f 'Bearer { os.environ[ "ULPI_API_KEY" ] } ' ,
'Content-Type' : 'application/json'
},
json = { 'query' : query, 'limit' : 3 }
)
results = response.json()
# Format response
blocks = [{
'type' : 'section' ,
'text' : {
'type' : 'mrkdwn' ,
'text' : f "* { r[ 'file' ] } * ( { r[ 'repository' ] } ) \n { r[ 'content' ][: 300 ] } ... \n < { r[ 'url' ] } |View on GitHub>"
}
} for r in results[ 'results' ]]
respond(
text = f "Found { results[ 'meta' ][ 'total' ] } results for \" { query } \" :" ,
blocks = blocks
)
if __name__ == "__main__" :
handler = SocketModeHandler(app, os.environ[ "SLACK_APP_TOKEN" ])
handler.start()
Deployment:
Deploy to Heroku, AWS Lambda, or any Node/Python host
Set environment variables: SLACK_BOT_TOKEN, ULPI_API_KEY
Invite bot to Slack channels
Result: Instant documentation lookup without leaving Slack
π Internal Documentation Portal
Use case: Company-branded docs website with ULPI searchBenefits:
Centralized documentation access
Advanced search with filters
Usage analytics (whatβs searched most)
SSO integration
Implementation (Next.js): API Route
Search Component
Result Display
// pages/api/search.ts
import type { NextApiRequest , NextApiResponse } from 'next' ;
export default async function handler (
req : NextApiRequest ,
res : NextApiResponse
) {
if ( req . method !== 'POST' ) {
return res . status ( 405 ). json ({ error: 'Method not allowed' });
}
const { query , repository , limit = 10 } = req . body ;
// Call ULPI API (server-side keeps API key secure)
const response = await fetch (
'https://api.ulpi.io/api/v1/documentation/search' ,
{
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ process . env . ULPI_API_KEY } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({ query , repository , limit })
}
);
const data = await response . json ();
// Log for analytics
await logSearch ( query , data . meta . total );
res . status ( 200 ). json ( data );
}
// components/SearchBar.tsx
import { useState } from 'react' ;
export default function SearchBar () {
const [ query , setQuery ] = useState ( '' );
const [ results , setResults ] = useState ([]);
const [ loading , setLoading ] = useState ( false );
const handleSearch = async ( q : string ) => {
setLoading ( true );
const res = await fetch ( '/api/search' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({ query: q })
});
const data = await res . json ();
setResults ( data . results );
setLoading ( false );
};
return (
< div className = "search-container" >
< input
type = "text"
placeholder = "Search documentation..."
value = { query }
onChange = {(e) => {
setQuery ( e . target . value );
if ( e . target . value . length > 2 ) {
handleSearch ( e . target . value );
}
}}
/>
{ loading && < Spinner />}
< div className = "results" >
{ results . map (( result ) => (
< SearchResult key = {result. url } result = { result } />
))}
</ div >
</ div >
);
}
// components/SearchResult.tsx
interface SearchResultProps {
result : {
file : string ;
repository : string ;
content : string ;
url : string ;
score : number ;
last_updated : string ;
};
}
export default function SearchResult ({ result } : SearchResultProps ) {
return (
< div className = "result-card" >
< div className = "result-header" >
< h3 >{result. file } </ h3 >
< span className = "repo-badge" > {result. repository } </ span >
< span className = "score" >
{ Math . round ( result . score * 100)} % match
</ span >
</ div >
< p className = "result-content" >
{ result . content . substring (0, 300)} ...
</ p >
< div className = "result-footer" >
< a href = {result. url } target = "_blank" rel = "noopener" >
View on GitHub β
</ a >
< span className = "last-updated" >
Updated { new Date ( result . last_updated ). toLocaleDateString ()}
</ span >
</ div >
</ div >
);
}
Features to add:
Repository filter dropdown
Branch selector
Search history
Bookmarks
Analytics dashboard
Example: docs.acmecorp.com - internal portal with 1,000+ daily searches
π₯οΈ CLI Tool for Terminal Search
Use case: Customer support chatbot that searches internal docsBenefits:
Accurate answers from YOUR documentation
Reduces support ticket volume
24/7 availability
Cites sources (links to docs)
Implementation (LangChain + OpenAI): # support_bot.py
from langchain.chat_models import ChatOpenAI
from langchain.agents import Tool, initialize_agent
import requests
import os
def search_documentation ( query : str ) -> str :
"""Search company documentation."""
response = requests.post(
'https://api.ulpi.io/api/v1/documentation/search' ,
headers = {
'Authorization' : f 'Bearer { os.environ[ "ULPI_API_KEY" ] } ' ,
'Content-Type' : 'application/json'
},
json = { 'query' : query, 'limit' : 3 }
)
results = response.json()[ 'results' ]
# Format for LLM
docs_text = " \n\n " .join([
f "From { r[ 'file' ] } : \n { r[ 'content' ] } \n Source: { r[ 'url' ] } "
for r in results
])
return docs_text
# Create tool for LangChain
doc_search_tool = Tool(
name = "SearchDocumentation" ,
func = search_documentation,
description = "Search company documentation for technical information, "
"deployment guides, troubleshooting steps, etc."
)
# Initialize agent
llm = ChatOpenAI( temperature = 0 , model = "gpt-4" )
agent = initialize_agent(
tools = [doc_search_tool],
llm = llm,
agent = "zero-shot-react-description" ,
verbose = True
)
# Use agent
response = agent.run(
"How do I deploy our backend API to production?"
)
print (response)
# Agent automatically calls search_documentation tool,
# gets relevant docs, and formulates answer with citations
Deploy as:
Slack bot
Website chat widget
Support ticket assistant
Email autoresponder
API Reference
Complete API endpoint documentation:
Authentication
All requests require API key in Authorization header:
Authorization: Bearer ulpi_live_sk_abc123...
API key formats:
ulpi_live_sk_... - Production environment
ulpi_test_sk_... - Testing environment
Generate keys: app.ulpi.io/api-keys
POST /api/v1/documentation/search
Search across indexed documentation
Base URL: https://api.ulpi.io/api/v1/documentation/search
Request Body:
{
"query" : "How do I deploy to production?" ,
"repository" : "backend-api" ,
"branch" : "main" ,
"limit" : 10
}
Parameters:
Parameter Type Required Description Default
querystring β
Yes Natural language search query - repositorystring β No Filter to specific repository All repos branchstring β No Search specific branch Default branch limitinteger β No Max results (1-20) 10
Query Guidelines:
Max length: 500 characters
Natural language: βHow do Iβ¦β works better than keywords
Be specific: βdeploy to AWS productionβ better than βdeployβ
Response:
{
"results" : [
{
"content" : "Full text excerpt from documentation..." ,
"file" : "docs/deployment.md" ,
"repository" : "backend-api" ,
"branch" : "main" ,
"url" : "https://github.com/org/repo/blob/main/docs/deployment.md#L42" ,
"score" : 0.94 ,
"last_updated" : "2025-01-15T10:30:00Z"
}
],
"meta" : {
"total" : 12 ,
"query_time_ms" : 45 ,
"cached" : false
}
}
Response Fields:
Field Type Description
results[]array Array of search results (sorted by relevance) results[].contentstring Documentation excerpt (most relevant section) results[].filestring File path relative to repository root results[].repositorystring Repository name results[].branchstring Git branch results[].urlstring Direct link to file on GitHub/GitLab results[].scorefloat Relevance score (0.0-1.0, higher = better match) results[].last_updatedstring ISO 8601 timestamp of last file update meta.totalinteger Total results found (may be more than returned) meta.query_time_msinteger Query execution time in milliseconds meta.cachedboolean Whether results were served from cache
Code Examples by Language
JavaScript / TypeScript
Python
PHP
Go
Node.js implementation: // ulpi-client.ts
interface SearchOptions {
repository ?: string ;
branch ?: string ;
limit ?: number ;
}
interface SearchResult {
content : string ;
file : string ;
repository : string ;
branch : string ;
url : string ;
score : number ;
last_updated : string ;
}
interface SearchResponse {
results : SearchResult [];
meta : {
total : number ;
query_time_ms : number ;
cached : boolean ;
};
}
class ULPIClient {
private apiKey : string ;
private baseUrl = 'https://api.ulpi.io/api/v1' ;
constructor ( apiKey : string ) {
this . apiKey = apiKey ;
}
async searchDocumentation (
query : string ,
options : SearchOptions = {}
) : Promise < SearchResponse > {
const response = await fetch ( ` ${ this . baseUrl } /documentation/search` , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ this . apiKey } ` ,
'Content-Type' : 'application/json' ,
},
body: JSON . stringify ({
query ,
repository: options . repository ,
branch: options . branch ,
limit: options . limit || 10 ,
}),
});
if ( ! response . ok ) {
const error = await response . json ();
throw new Error ( `ULPI API error: ${ error . message } ` );
}
return response . json ();
}
}
// Usage
const ulpi = new ULPIClient ( process . env . ULPI_API_KEY ! );
const results = await ulpi . searchDocumentation (
'How to deploy to production?' ,
{ repository: 'backend-api' , limit: 5 }
);
results . results . forEach ( result => {
console . log ( ` ${ result . file } : ${ result . content . substring ( 0 , 100 ) } ...` );
console . log ( `Score: ${ result . score } , URL: ${ result . url } \n ` );
});
Python implementation with requests: # ulpi_client.py
import requests
from typing import Optional, List, Dict
import os
class ULPIClient :
"""Client for ULPI Documentation API."""
def __init__ ( self , api_key : str ):
self .api_key = api_key
self .base_url = 'https://api.ulpi.io/api/v1'
def search_documentation (
self ,
query : str ,
repository : Optional[ str ] = None ,
branch : Optional[ str ] = None ,
limit : int = 10
) -> Dict:
"""
Search documentation across repositories.
Args:
query: Natural language search query
repository: Optional repository filter
branch: Optional branch filter
limit: Max results (1-20)
Returns:
Dict with 'results' and 'meta' keys
Raises:
requests.HTTPError: If API request fails
"""
response = requests.post(
f ' { self .base_url } /documentation/search' ,
headers = {
'Authorization' : f 'Bearer { self .api_key } ' ,
'Content-Type' : 'application/json'
},
json = {
'query' : query,
'repository' : repository,
'branch' : branch,
'limit' : limit
}
)
response.raise_for_status()
return response.json()
# Usage
ulpi = ULPIClient(os.getenv( 'ULPI_API_KEY' ))
results = ulpi.search_documentation(
'How to deploy to production?' ,
repository = 'backend-api' ,
limit = 5
)
for result in results[ 'results' ]:
print ( f " { result[ 'file' ] } (score: { result[ 'score' ] :.2f} )" )
print ( f " { result[ 'content' ][: 150 ] } ..." )
print ( f " { result[ 'url' ] } \n " )
print ( f "Found { results[ 'meta' ][ 'total' ] } results in { results[ 'meta' ][ 'query_time_ms' ] } ms" )
PHP implementation with Laravel HTTP: <? php
// app/Services/UlpiClient.php
namespace App\Services ;
use Illuminate\Support\Facades\ Http ;
use Illuminate\Support\Facades\ Cache ;
class UlpiClient
{
private string $apiKey ;
private string $baseUrl = 'https://api.ulpi.io/api/v1' ;
public function __construct ( string $apiKey )
{
$this -> apiKey = $apiKey ;
}
/**
* Search documentation
*/
public function searchDocumentation (
string $query ,
? string $repository = null ,
? string $branch = null ,
int $limit = 10
) : array {
// Cache key
$cacheKey = "ulpi:search:" . md5 ( json_encode ([
'query' => $query ,
'repository' => $repository ,
'branch' => $branch ,
'limit' => $limit
]));
// Check cache (5 minutes)
return Cache :: remember ( $cacheKey , 300 , function () use (
$query , $repository , $branch , $limit
) {
$response = Http :: withHeaders ([
'Authorization' => "Bearer { $this -> apiKey }" ,
]) -> post ( "{ $this -> baseUrl }/documentation/search" , [
'query' => $query ,
'repository' => $repository ,
'branch' => $branch ,
'limit' => $limit ,
]);
if ( $response -> failed ()) {
throw new \Exception (
"ULPI API error: " . $response -> json ( 'message' )
);
}
return $response -> json ();
});
}
/**
* Get first result for quick lookups
*/
public function quickSearch ( string $query ) : ? array
{
$results = $this -> searchDocumentation ( $query , limit : 1 );
return $results [ 'results' ][ 0 ] ?? null ;
}
}
// Usage (in controller or service)
$ulpi = new UlpiClient ( config ( 'services.ulpi.api_key' ));
$results = $ulpi -> searchDocumentation (
'How to deploy to production?' ,
repository : 'backend-api' ,
limit : 5
);
foreach ( $results [ 'results' ] as $result ) {
echo "{ $result ['file']}: { $result ['content']} \n " ;
echo "{ $result ['url']} \n\n " ;
}
Go implementation: // ulpi/client.go
package ulpi
import (
" bytes "
" encoding/json "
" fmt "
" net/http "
" time "
)
type Client struct {
APIKey string
BaseURL string
client * http . Client
}
type SearchOptions struct {
Repository string `json:"repository,omitempty"`
Branch string `json:"branch,omitempty"`
Limit int `json:"limit,omitempty"`
}
type SearchResult struct {
Content string `json:"content"`
File string `json:"file"`
Repository string `json:"repository"`
Branch string `json:"branch"`
URL string `json:"url"`
Score float64 `json:"score"`
LastUpdated time . Time `json:"last_updated"`
}
type SearchResponse struct {
Results [] SearchResult `json:"results"`
Meta struct {
Total int `json:"total"`
QueryTimeMs int `json:"query_time_ms"`
Cached bool `json:"cached"`
} `json:"meta"`
}
func NewClient ( apiKey string ) * Client {
return & Client {
APIKey : apiKey ,
BaseURL : "https://api.ulpi.io/api/v1" ,
client : & http . Client { Timeout : 30 * time . Second },
}
}
func ( c * Client ) SearchDocumentation (
query string ,
opts * SearchOptions ,
) ( * SearchResponse , error ) {
if opts == nil {
opts = & SearchOptions { Limit : 10 }
}
payload := map [ string ] interface {}{
"query" : query ,
"repository" : opts . Repository ,
"branch" : opts . Branch ,
"limit" : opts . Limit ,
}
jsonData , err := json . Marshal ( payload )
if err != nil {
return nil , err
}
req , err := http . NewRequest (
"POST" ,
fmt . Sprintf ( " %s /documentation/search" , c . BaseURL ),
bytes . NewBuffer ( jsonData ),
)
if err != nil {
return nil , err
}
req . Header . Set ( "Authorization" , fmt . Sprintf ( "Bearer %s " , c . APIKey ))
req . Header . Set ( "Content-Type" , "application/json" )
resp , err := c . client . Do ( req )
if err != nil {
return nil , err
}
defer resp . Body . Close ()
if resp . StatusCode != http . StatusOK {
return nil , fmt . Errorf ( "API error: %s " , resp . Status )
}
var searchResp SearchResponse
if err := json . NewDecoder ( resp . Body ). Decode ( & searchResp ); err != nil {
return nil , err
}
return & searchResp , nil
}
// Usage
package main
import (
" fmt "
" log "
" os "
)
func main () {
client := ulpi . NewClient ( os . Getenv ( "ULPI_API_KEY" ))
results , err := client . SearchDocumentation (
"How to deploy to production?" ,
& ulpi . SearchOptions {
Repository : "backend-api" ,
Limit : 5 ,
},
)
if err != nil {
log . Fatal ( err )
}
for _ , result := range results . Results {
fmt . Printf ( " %s (score: %.2f ) \n " , result . File , result . Score )
fmt . Printf ( " %s \n " , result . Content [: 150 ])
fmt . Printf ( " %s \n\n " , result . URL )
}
fmt . Printf ( "Found %d results in %d ms \n " ,
results . Meta . Total ,
results . Meta . QueryTimeMs )
}
Rate Limits & Quotas
API limits by plan:
Plan Requests/Min Requests/Hour Requests/Day Burst
Starter 60 1,000 10,000 100 Professional 120 5,000 50,000 200 Enterprise 300 15,000 Unlimited 500
Rate limit headers (included in every response):
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1640000000
429 Too Many Requests response:
{
"error" : "Rate limit exceeded" ,
"message" : "You have exceeded your rate limit of 60 requests per minute" ,
"retry_after" : 30
}
Best practices:
Implement exponential backoff
Cache results (5-15 minutes)
Monitor X-RateLimit-Remaining
Use batch queries when possible
Error Handling
HTTP status codes:
Cause: Invalid request parameters{
"error" : "Invalid request" ,
"message" : "Query parameter is required" ,
"field" : "query"
}
Common issues:
Missing query parameter
limit out of range (1-20)
query too long (>500 chars)
Fix: Validate parameters before sendingCause: Invalid or missing API key{
"error" : "Unauthorized" ,
"message" : "Invalid API key"
}
Common issues:
API key not in Authorization header
Wrong key format
Expired or revoked key
Fix: Verify API key and header formatCause: API key lacks access to resource{
"error" : "Forbidden" ,
"message" : "API key does not have access to repository 'backend-api'"
}
Fix: Check API key scopes or connect repositoryCause: Resource doesnβt exist{
"error" : "Not found" ,
"message" : "Repository 'backend-api' not found"
}
Fix: Verify repository name or connect repositoryCause: Rate limit exceeded{
"error" : "Rate limit exceeded" ,
"retry_after" : 30
}
Fix: Implement backoff, cache results, or upgrade planCause: Server-side issue{
"error" : "Internal server error" ,
"message" : "Search service temporarily unavailable"
}
Fix: Retry with exponential backoff. Contact support if persistent.
Robust error handling:
async function searchWithRetry (
query : string ,
maxRetries = 3
) : Promise < SearchResponse > {
for ( let attempt = 1 ; attempt <= maxRetries ; attempt ++ ) {
try {
return await ulpi . searchDocumentation ( query );
} catch ( error ) {
// Don't retry client errors (4xx)
if ( error . status >= 400 && error . status < 500 ) {
throw error ;
}
// Last attempt - throw error
if ( attempt === maxRetries ) {
throw error ;
}
// Exponential backoff: 1s, 2s, 4s
const delay = Math . pow ( 2 , attempt - 1 ) * 1000 ;
await new Promise ( resolve => setTimeout ( resolve , delay ));
}
}
}
Best Practices
ποΈ Cache Results Aggressively
Cache search results to reduce API calls: // Simple in-memory cache with TTL
class SearchCache {
private cache = new Map < string , { data : any ; expires : number }>();
set ( key : string , data : any , ttl = 300000 ) { // 5 min default
this . cache . set ( key , {
data ,
expires: Date . now () + ttl
});
}
get ( key : string ) : any | null {
const item = this . cache . get ( key );
if ( ! item ) return null ;
if ( Date . now () > item . expires ) {
this . cache . delete ( key );
return null ;
}
return item . data ;
}
}
const cache = new SearchCache ();
async function cachedSearch ( query : string , options : any ) {
const cacheKey = JSON . stringify ({ query , ... options });
// Check cache first
const cached = cache . get ( cacheKey );
if ( cached ) {
return { ... cached , meta: { ... cached . meta , cached: true } };
}
// Cache miss - call API
const results = await ulpi . searchDocumentation ( query , options );
cache . set ( cacheKey , results );
return results ;
}
Recommended TTL:
User searches: 5-15 minutes
Programmatic queries: 1-5 minutes
Static content: 1 hour
Cache invalidation:
After repository webhook (if you process webhooks)
Manual refresh button
Time-based expiry
NEVER expose API keys in client-side code: // β BAD: Client-side code (key exposed in browser)
// frontend/search.ts
const results = await fetch ( 'https://api.ulpi.io/api/v1/documentation/search' , {
headers: {
'Authorization' : 'Bearer ulpi_live_sk_abc123...' // EXPOSED!
}
});
// β
GOOD: Proxy through your backend
// frontend/search.ts
const results = await fetch ( '/api/search' , {
method: 'POST' ,
body: JSON . stringify ({ query: 'authentication' })
});
// backend/api/search.ts
export async function POST ( req : Request ) {
const { query } = await req . json ();
// API key stays on server
const results = await fetch ( 'https://api.ulpi.io/api/v1/documentation/search' , {
headers: {
'Authorization' : `Bearer ${ process . env . ULPI_API_KEY } ` // SECURE
},
body: JSON . stringify ({ query })
});
return results . json ();
}
Storage best practices:
Environment variables (.env file, gitignored)
Secret managers (AWS Secrets Manager, HashiCorp Vault)
Never commit to version control
Rotate keys quarterly
β‘ Implement Exponential Backoff
Retry failed requests with increasing delays: async function fetchWithBackoff < T >(
fn : () => Promise < T >,
maxRetries = 3
) : Promise < T > {
for ( let attempt = 0 ; attempt < maxRetries ; attempt ++ ) {
try {
return await fn ();
} catch ( error : any ) {
// Don't retry client errors (4xx)
if ( error . status >= 400 && error . status < 500 ) {
throw error ;
}
// Last attempt
if ( attempt === maxRetries - 1 ) {
throw error ;
}
// Exponential backoff: 1s, 2s, 4s
const delay = Math . pow ( 2 , attempt ) * 1000 ;
// Add jitter to prevent thundering herd
const jitter = Math . random () * 1000 ;
await new Promise ( resolve => setTimeout ( resolve , delay + jitter ));
}
}
throw new Error ( 'Max retries exceeded' );
}
// Usage
const results = await fetchWithBackoff (() =>
ulpi . searchDocumentation ( 'deploy to production' )
);
When to retry:
β
500-level errors (server issues)
β
Network errors (timeouts, connection refused)
β
429 (but respect retry_after header)
β 400-level errors (client mistakes - fix your code)
π Monitor Usage & Performance
π Optimize Search Queries
Write better queries for better results: // β Bad: Too generic
await ulpi . searchDocumentation ( 'database' );
// Returns 127 results, mostly irrelevant
// β
Good: Specific question
await ulpi . searchDocumentation ( 'How to optimize slow PostgreSQL queries?' );
// Returns 3 highly relevant results
// β
Better: Include context
await ulpi . searchDocumentation (
'How to configure Redis cache for API session storage?' ,
{ repository: 'backend-api' }
);
// Scoped to relevant repo, precise results
// Helper function for query optimization
function buildQuery (
topic : string ,
context ?: string ,
action ?: 'how' | 'what' | 'where'
) : string {
const actionMap = {
how: 'How do I' ,
what: 'What is' ,
where: 'Where is'
};
const prefix = action ? actionMap [ action ] : '' ;
const suffix = context ? `in ${ context } ` : '' ;
return ` ${ prefix } ${ topic } ${ suffix } ` . trim ();
}
// Usage
const query = buildQuery ( 'deploy' , 'production environment' , 'how' );
// "How do I deploy in production environment"
Query best practices:
Be specific, not generic
Use question format
Include technology names
Specify environment/context
Use repository filter when known
Webhooks (Optional)
Get notified when documentation updates:
Coming soon: Webhook support for documentation changes.Subscribe to:
documentation.indexed - Repository finished indexing
documentation.updated - Documentation file changed
documentation.deleted - Documentation file removed
Request early access β
Next Steps
Generate API Keys Create production API keys for your integration Start building today
Search Features Learn about query syntax and advanced filters Optimize your queries
How It Works Understand semantic search architecture and performance Deep dive into technology
Repository Management Connect repositories to make them searchable via API Expand your search scope
Need help with API integration? Average response time: Under 2 hours during business hoursLooking for client libraries? We have official SDKs for JavaScript, Python, PHP, and Go.Want to contribute? We accept community SDKs for other languages!