Skip to main content
Cookie preferences

We use analytics cookies to understand usage and improve CleanTextLab. You can accept or decline Privacy policy. Manage preferences.

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:

  1. Upgrade to Pro at cleantextlab.com/pricing
  2. Sign up or sign in to your account
  3. Navigate to Settings → Plan & API Access
  4. Click "Load Keys" to view your API keys
  5. 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:3000 or http://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.

EndpointRate LimitWindow
/v1/run5,000 requests24 hours
/v1/sanitize5,000 requests24 hours
/v1/sort-dedupe5,000 requests24 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.

  1. Set Method: POST
  2. Set URL: https://cleantextlab.com/api/v1/run (or /sanitize, /sort-dedupe)
  3. Add headers:
    • Content-Type: application/json
    • x-api-key: YOUR_API_KEY
  4. 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:

FieldTypeRequiredDescription
inputstringYesInput text to process
stepsarrayYesArray 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:

FieldTypeRequiredDescriptionMax
urlsarrayYes*Array of URLs to sanitize500 URLs
urlstringYes*Single URL to sanitize-
inputstringYes*Newline-separated URLs500 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:

FieldTypeDefaultDescriptionMax
linesarray-Array of strings to process5,000 lines
inputstring-Newline-separated text5,000 lines
trimLinesbooleantrueRemove leading/trailing whitespace-
dropBlankbooleantrueRemove empty lines-
dedupebooleantrueRemove duplicate lines-
ignoreCasebooleanfalseCase-insensitive deduplication-
sortbooleantrueSort lines alphabetically-
directionstring"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:

  1. Trim - Remove whitespace (if trimLines: true)
  2. Filter - Remove blank lines (if dropBlank: true)
  3. Deduplicate - Remove duplicates (if dedupe: true)
  4. Sort - Sort alphabetically (if sort: true)

Error Codes

Status CodeErrorDescription
200SuccessRequest processed successfully
400Bad RequestInvalid request body or parameters
401UnauthorizedMissing or invalid API key
429Too Many RequestsRate limit exceeded
500Internal Server ErrorServer 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:

Want to Contribute?

We welcome community-contributed SDKs! If you've built a wrapper library:

  1. Open a PR to add it to this documentation
  2. Follow our SDK Guidelines
  3. Include tests and examples

Support & Feedback


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

Complete API Reference | CleanTextLab