Docs / API
CleanTextLab API Reference
Version: 1.4.0
Base URL: https://cleantextlab.com/api/v1
Last Updated: January 5, 2026
📚 Architecture Documentation
For comprehensive understanding of the system:
- ARCHITECTURE - Complete system overview, folder structure, and design decisions
- TOOL_REGISTRY - Catalog of all 35+ tools with specifications
- PATTERNS - Reusable code patterns and implementation examples
- DEPENDENCIES - External services, NPM packages, and dependency graph
- DATA_FLOW - How data moves through the system
Quick Architecture Facts:
- Framework: Next.js 15.5.9 + React 19
- Auth: Clerk (OAuth 2.0)
- Storage: Upstash Redis (REST KV; E2E encrypted history)
- Hosting: Vercel Edge Functions
- Processing: Client-side + API workflows
- Rate Limiting: Pro only - 5,000 req/day
- CORS: Two-layer validation (preflight whitelist + per-key allowlist)
Table of Contents
Authentication
All API requests require authentication via an API key. Include your key in the x-api-key header:
curl -H "x-api-key: YOUR_API_KEY" \
https://cleantextlab.com/api/v1/sanitize
Getting an API Key
For Pro Users:
- Upgrade to Pro at cleantextlab.com/pricing
- Sign up or sign in to your account
- Navigate to Settings → Plan & API Access
- Click "Load Keys" to view your API keys
- Pro: 5,000 requests per day
Production vs Development Keys:
- Production key (
ctl_live_...): Use in deployed apps and server-side integrations. - Development key (
ctl_dev_...): Use for local testing (e.g.,http://localhost:3000orhttp://localhost:5173). - Shared limits: Production + Development keys share the same 5,000/day quota.
- CORS allowlist: Browser-based clients must add their origin per key in Settings → Plan & API Access.
For Administrators:
API keys can be programmatically created using the admin endpoint (requires API_ADMIN_TOKEN). Only Pro keys are allowed:
curl -X POST https://cleantextlab.com/api/keys \
-H "x-admin-token: YOUR_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"plan": "pro"}'
Rate Limits
API access requires a Pro subscription.
| Endpoint | Rate Limit | Window |
|---|---|---|
/v1/run | 5,000 requests | 24 hours |
/v1/sanitize | 5,000 requests | 24 hours |
/v1/sort-dedupe | 5,000 requests | 24 hours |
When you exceed the rate limit, the API returns a 429 Too Many Requests error:
{
"error": "Rate limit exceeded"
}
Rate Limit Headers:
Rate limit headers are not currently returned; rely on status 429 for enforcement feedback.
Burst Limits (Abuse Protection):
- Per-IP burst: 120 requests/minute
- Per-key burst: 120 requests/minute
Payload Limits:
- Max input size: 500,000 characters
- Max steps per
/v1/run: 25 - List endpoints (
/sanitize,/sort-dedupe) cap total input at 1,000,000 characters
n8n Quickstart
Use n8n’s HTTP Request node to call CleanTextLab.
- Set Method:
POST - Set URL:
https://cleantextlab.com/api/v1/run(or/sanitize,/sort-dedupe) - Add headers:
Content-Type: application/jsonx-api-key: YOUR_API_KEY
- Paste your JSON body.
For production workflows, enable n8n’s node-level retries and batching for smoother handling of 429 responses.
Full guide: https://cleantextlab.com/docs/n8n
Infrastructure for Agents (MCP)
CleanTextLab provides a Model Context Protocol (MCP) server, allowing AI agents like Claude to use our tools directly.
- Setup Guide: MCP Setup
- Included: All API endpoints plus 10+ utility tools.
Endpoints
POST /v1/run
Run a workflow of tools on a single input string.
Endpoint: https://cleantextlab.com/api/v1/run
Request Headers:
x-api-key: YOUR_API_KEY
Content-Type: application/json
CORS (browser usage):
The API uses a per-key origin allowlist for browser-based clients. Manage allowed origins in Settings → Plan & API Access (dev keys can use http://localhost:3000 or http://localhost:5173). Server-side integrations (n8n, MCP, backend services) do not require CORS.
Access-Control-Allow-Origin: https://your-app.com
Access-Control-Allow-Headers: Content-Type, x-api-key
Access-Control-Allow-Methods: POST, OPTIONS
Request Body:
{
"input": "Hello world",
"steps": ["line-break-remover", "title-case-converter"]
}
Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
input | string | Yes | Input text to process |
steps | array | Yes | Array of step IDs or objects with toolSlug |
Configurable steps:
You can pass a tool configuration object in the steps array when a tool supports options.
{
"input": "user@example.com;admin@example.com",
"steps": [
{
"toolSlug": "csv-json-converter",
"config": {
"delimiter": ";",
"hasHeaders": false
}
}
]
}
Response:
{
"result": "Hello World",
"meta": {
"stepsExecuted": 2,
"inputLength": 11,
"outputLength": 11,
"processingTimeMs": 3
}
}
Example Request (cURL):
curl -X POST https://cleantextlab.com/api/v1/run \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": "hello world",
"steps": ["title-case-converter"]
}'
Example (ASCII Tree Generator):
curl -X POST https://cleantextlab.com/api/v1/run \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": "src/app/layout.tsx\nsrc/components/Nav.tsx\nREADME.md",
"steps": ["ascii-tree-generator"]
}'
POST /v1/sanitize
Remove tracking parameters and clean URLs in bulk. Pro plan required.
Endpoint: https://cleantextlab.com/api/v1/sanitize
Request Headers:
x-api-key: YOUR_API_KEY
Content-Type: application/json
Request Body:
{
"urls": [
"https://example.com?utm_source=twitter&utm_campaign=spring",
"https://shop.example.com?ref=affiliate123&fbclid=xyz"
]
}
Alternative Formats:
Single URL:
{
"url": "https://example.com?utm_source=twitter"
}
Newline-separated input:
{
"input": "https://example.com?utm_source=twitter\nhttps://shop.example.com?ref=affiliate123"
}
Parameters:
| Field | Type | Required | Description | Max |
|---|---|---|---|---|
urls | array | Yes* | Array of URLs to sanitize | 500 URLs |
url | string | Yes* | Single URL to sanitize | - |
input | string | Yes* | Newline-separated URLs | 500 URLs |
*One of urls, url, or input is required
Response:
{
"sanitized": [
"https://example.com",
"https://shop.example.com"
],
"count": 2
}
Removed Parameters:
The API removes these tracking parameters:
- UTM parameters:
utm_source,utm_medium,utm_campaign,utm_term,utm_content - Facebook:
fbclid,fb_action_ids,fb_action_types,fb_source - Google:
gclid,gclsrc,dclid,gbraid,wbraid - Twitter/X:
twclid,s - Email:
mc_cid,mc_eid - Affiliate:
ref,referrer,source - Tracking:
_hsenc,_hsmi,mkt_tok
Example Request (cURL):
curl -X POST https://cleantextlab.com/api/v1/sanitize \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"urls": [
"https://example.com?utm_source=twitter&utm_campaign=spring",
"https://shop.example.com?ref=affiliate123&fbclid=xyz"
]
}'
Example Request (JavaScript):
const response = await fetch('https://cleantextlab.com/api/v1/sanitize', {
method: 'POST',
headers: {
'x-api-key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
urls: [
'https://example.com?utm_source=twitter&utm_campaign=spring',
'https://shop.example.com?ref=affiliate123&fbclid=xyz'
]
})
});
const data = await response.json();
console.log(data.sanitized);
// ["https://example.com", "https://shop.example.com"]
POST /v1/sort-dedupe
Sort and deduplicate lines of text with configurable options. Pro plan required.
Endpoint: https://cleantextlab.com/api/v1/sort-dedupe
Request Headers:
x-api-key: YOUR_API_KEY
Content-Type: application/json
Request Body:
{
"lines": [
"apple",
"banana",
"apple",
" cherry ",
"",
"Banana"
],
"trimLines": true,
"dropBlank": true,
"dedupe": true,
"ignoreCase": true,
"sort": true,
"direction": "asc"
}
Alternative Format:
Newline-separated input:
{
"input": "apple\nbanana\napple\n cherry \n\nBanana",
"trimLines": true,
"dedupe": true,
"sort": true
}
Parameters:
| Field | Type | Default | Description | Max |
|---|---|---|---|---|
lines | array | - | Array of strings to process | 5,000 lines |
input | string | - | Newline-separated text | 5,000 lines |
trimLines | boolean | true | Remove leading/trailing whitespace | - |
dropBlank | boolean | true | Remove empty lines | - |
dedupe | boolean | true | Remove duplicate lines | - |
ignoreCase | boolean | false | Case-insensitive deduplication | - |
sort | boolean | true | Sort lines alphabetically | - |
direction | string | "asc" | Sort direction: "asc" or "desc" | - |
Response:
{
"output": "apple\nbanana\ncherry",
"lines": 3
}
Example Request (cURL):
curl -X POST https://cleantextlab.com/api/v1/sort-dedupe \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": "apple\nbanana\napple\ncherry\n\nbanana",
"dedupe": true,
"sort": true,
"direction": "asc"
}'
Example Request (Python):
import requests
response = requests.post(
'https://cleantextlab.com/api/v1/sort-dedupe',
headers={
'x-api-key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
json={
'lines': ['apple', 'banana', 'apple', ' cherry ', '', 'Banana'],
'trimLines': True,
'dropBlank': True,
'dedupe': True,
'ignoreCase': True,
'sort': True,
'direction': 'asc'
}
)
data = response.json()
print(data['output'])
# apple
# banana
# cherry
Processing Pipeline:
The API processes lines in this order:
- Trim - Remove whitespace (if
trimLines: true) - Filter - Remove blank lines (if
dropBlank: true) - Deduplicate - Remove duplicates (if
dedupe: true) - Sort - Sort alphabetically (if
sort: true)
Error Codes
| Status Code | Error | Description |
|---|---|---|
200 | Success | Request processed successfully |
400 | Bad Request | Invalid request body or parameters |
401 | Unauthorized | Missing or invalid API key |
429 | Too Many Requests | Rate limit exceeded |
500 | Internal Server Error | Server error (contact support) |
Error Response Format:
{
"error": "Missing API key"
}
Common Errors:
Missing API Key:
{
"error": "Missing API key"
}
Solution: Include x-api-key header in your request.
Invalid API Key:
{
"error": "Invalid API key"
}
Solution: Verify your API key is correct and not revoked.
Rate Limit Exceeded:
{
"error": "Rate limit exceeded"
}
Solution: Wait for the rate limit window to reset or upgrade to Pro.
No URLs/Lines Provided:
{
"error": "No URLs provided"
}
// or
{
"error": "No lines provided"
}
Solution: Include urls, url, or input in your request body.
Code Examples
JavaScript (Node.js)
const axios = require('axios');
const API_KEY = 'YOUR_API_KEY';
const BASE_URL = 'https://cleantextlab.com/api/v1';
// Sanitize URLs
async function sanitizeUrls(urls) {
try {
const response = await axios.post(`${BASE_URL}/sanitize`,
{ urls },
{ headers: { 'x-api-key': API_KEY } }
);
return response.data.sanitized;
} catch (error) {
console.error('Error:', error.response?.data || error.message);
}
}
// Sort & Dedupe
async function sortAndDedupe(lines) {
try {
const response = await axios.post(`${BASE_URL}/sort-dedupe`,
{
lines,
dedupe: true,
sort: true,
ignoreCase: true
},
{ headers: { 'x-api-key': API_KEY } }
);
return response.data.output.split('\n');
} catch (error) {
console.error('Error:', error.response?.data || error.message);
}
}
// Usage
(async () => {
const cleanUrls = await sanitizeUrls([
'https://example.com?utm_source=twitter',
'https://shop.com?ref=aff123&fbclid=xyz'
]);
console.log('Clean URLs:', cleanUrls);
const sortedLines = await sortAndDedupe(['apple', 'Banana', 'apple', 'cherry']);
console.log('Sorted:', sortedLines);
})();
Python
import requests
API_KEY = 'YOUR_API_KEY'
BASE_URL = 'https://cleantextlab.com/api/v1'
class CleanTextLabAPI:
def __init__(self, api_key):
self.api_key = api_key
self.headers = {
'x-api-key': api_key,
'Content-Type': 'application/json'
}
def sanitize_urls(self, urls):
"""Remove tracking parameters from URLs"""
response = requests.post(
f'{BASE_URL}/sanitize',
headers=self.headers,
json={'urls': urls}
)
response.raise_for_status()
return response.json()['sanitized']
def sort_dedupe(self, lines, **options):
"""Sort and deduplicate lines"""
payload = {'lines': lines, **options}
response = requests.post(
f'{BASE_URL}/sort-dedupe',
headers=self.headers,
json=payload
)
response.raise_for_status()
return response.json()['output'].split('\n')
# Usage
api = CleanTextLabAPI(API_KEY)
# Sanitize URLs
clean_urls = api.sanitize_urls([
'https://example.com?utm_source=twitter',
'https://shop.com?ref=aff123&fbclid=xyz'
])
print('Clean URLs:', clean_urls)
# Sort & Dedupe
sorted_lines = api.sort_dedupe(
['apple', 'Banana', 'apple', 'cherry'],
dedupe=True,
sort=True,
ignoreCase=True
)
print('Sorted:', sorted_lines)
PHP
<?php
class CleanTextLabAPI {
private $apiKey;
private $baseUrl = 'https://cleantextlab.com/api/v1';
public function __construct($apiKey) {
$this->apiKey = $apiKey;
}
private function request($endpoint, $data) {
$ch = curl_init($this->baseUrl . $endpoint);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'x-api-key: ' . $this->apiKey,
'Content-Type: application/json'
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
throw new Exception("API Error: $response");
}
return json_decode($response, true);
}
public function sanitizeUrls($urls) {
$result = $this->request('/sanitize', ['urls' => $urls]);
return $result['sanitized'];
}
public function sortDedupe($lines, $options = []) {
$data = array_merge(['lines' => $lines], $options);
$result = $this->request('/sort-dedupe', $data);
return explode("\n", $result['output']);
}
}
// Usage
$api = new CleanTextLabAPI('YOUR_API_KEY');
$cleanUrls = $api->sanitizeUrls([
'https://example.com?utm_source=twitter',
'https://shop.com?ref=aff123&fbclid=xyz'
]);
print_r($cleanUrls);
$sortedLines = $api->sortDedupe(
['apple', 'Banana', 'apple', 'cherry'],
['dedupe' => true, 'sort' => true, 'ignoreCase' => true]
);
print_r($sortedLines);
Go
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
const (
APIKey = "YOUR_API_KEY"
BaseURL = "https://cleantextlab.com/api/v1"
)
type SanitizeRequest struct {
URLs []string `json:"urls"`
}
type SanitizeResponse struct {
Sanitized []string `json:"sanitized"`
Count int `json:"count"`
}
type SortDedupeRequest struct {
Lines []string `json:"lines"`
TrimLines bool `json:"trimLines"`
DropBlank bool `json:"dropBlank"`
Dedupe bool `json:"dedupe"`
IgnoreCase bool `json:"ignoreCase"`
Sort bool `json:"sort"`
Direction string `json:"direction"`
}
type SortDedupeResponse struct {
Output string `json:"output"`
Lines int `json:"lines"`
}
func makeRequest(endpoint string, payload interface{}, result interface{}) error {
body, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", BaseURL+endpoint, bytes.NewBuffer(body))
req.Header.Set("x-api-key", APIKey)
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
return json.Unmarshal(data, result)
}
func sanitizeURLs(urls []string) ([]string, error) {
req := SanitizeRequest{URLs: urls}
var resp SanitizeResponse
err := makeRequest("/sanitize", req, &resp)
return resp.Sanitized, err
}
func sortDedupe(lines []string) ([]string, error) {
req := SortDedupeRequest{
Lines: lines,
TrimLines: true,
DropBlank: true,
Dedupe: true,
IgnoreCase: true,
Sort: true,
Direction: "asc",
}
var resp SortDedupeResponse
err := makeRequest("/sort-dedupe", req, &resp)
if err != nil {
return nil, err
}
return strings.Split(resp.Output, "\n"), nil
}
func main() {
// Sanitize URLs
cleanURLs, _ := sanitizeURLs([]string{
"https://example.com?utm_source=twitter",
"https://shop.com?ref=aff123&fbclid=xyz",
})
fmt.Println("Clean URLs:", cleanURLs)
// Sort & Dedupe
sortedLines, _ := sortDedupe([]string{"apple", "Banana", "apple", "cherry"})
fmt.Println("Sorted:", sortedLines)
}
Ruby
require 'net/http'
require 'json'
require 'uri'
class CleanTextLabAPI
BASE_URL = 'https://cleantextlab.com/api/v1'
def initialize(api_key)
@api_key = api_key
end
def sanitize_urls(urls)
request('/sanitize', { urls: urls })['sanitized']
end
def sort_dedupe(lines, options = {})
data = { lines: lines }.merge(options)
result = request('/sort-dedupe', data)
result['output'].split("\n")
end
private
def request(endpoint, data)
uri = URI("#{BASE_URL}#{endpoint}")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.path)
request['x-api-key'] = @api_key
request['Content-Type'] = 'application/json'
request.body = data.to_json
response = http.request(request)
JSON.parse(response.body)
end
end
# Usage
api = CleanTextLabAPI.new('YOUR_API_KEY')
clean_urls = api.sanitize_urls([
'https://example.com?utm_source=twitter',
'https://shop.com?ref=aff123&fbclid=xyz'
])
puts "Clean URLs: #{clean_urls}"
sorted_lines = api.sort_dedupe(
['apple', 'Banana', 'apple', 'cherry'],
dedupe: true,
sort: true,
ignoreCase: true
)
puts "Sorted: #{sorted_lines}"
SDKs & Libraries
Official SDKs
We're working on official SDKs for popular languages. In the meantime, use the code examples above or contribute your own!
Community Libraries:
- cleantextlab-node - Node.js (Coming Soon)
- cleantextlab-python - Python (Coming Soon)
Want to Contribute?
We welcome community-contributed SDKs! If you've built a wrapper library:
- Open a PR to add it to this documentation
- Follow our SDK Guidelines
- Include tests and examples
Support & Feedback
- Documentation: cleantextlab.com/docs
- Bug Reports: github.com/cleantextlab/issues
- Feature Requests: cleantextlab.com/contact
- Email: support@cleantextlab.com
Changelog
Version 1.0 (January 2026)
- Initial API release
/v1/sanitize- URL sanitization endpoint/v1/sort-dedupe- Sort and deduplicate lines- Rate limiting: 5,000 req/day (Pro). Free tier available (10 req/day).
- MCP Server Support: Connect AI agents directly to CleanTextLab.
Happy Coding! 🚀
Built with ❤️ by the CleanTextLab team