API

DNC API Documentation for Do Not Call Scrubbing

Connect your CRM, dialer, or internal tool to scrub phone numbers against National DNC, state DNC, and TCPA litigator data.

Test your token ready
POST /api/v2/scrubs JSON request · 3,000 numbers per request
api.response ready for request
{
  "message": "Response will appear here after submit."
}
These examples are written as drop-in clients for older and current runtimes. Choose the runtime closest to your project, replace YOUR_TOKEN_HERE, then adjust the sample numbers, file path, scrub options, or webhook URL for your account.
POST /api/v2/scrubs
  • This endpoint scrubs an array of phone numbers.
  • Send your API token in the Authorization header as a Bearer token.
  • numbers must be an array of U.S. phone numbers. Formatting is accepted and normalized.
  • scrub_type is optional. When omitted, all supported scrub options enabled on your token are used.
  • Supported scrub options are dnc, state, and litigator.
  • Mobile/landline scrubbing is not available through API subscriptions.
  • Legacy requests containing mobile continue without mobile output. Other requested supported options are preserved; if none remain, the supported options enabled on the token are used.
  • For predictable latency, split very large lists into batches around 3,000 numbers.
POST /api/v2/scrub/file
  • This endpoint uploads a file to be scrubbed.
  • Send your API token in the Authorization header as a Bearer token.
  • file is required and accepts CSV, TSV, plain text, or modern Excel files.
  • scrub_type[] is optional. When omitted, all supported scrub options enabled on your account are used.
  • Legacy uploads containing mobile continue without mobile output and preserve other requested supported options.
  • scrub_column[] is required and uses zero-based column indexes. Use 0 for the first column.
  • webhook_url is optional and receives a POST when the export is finished.
PHP 5.4 DNC API client
<?php

final class DncProjectClient
{
    private $token;
    private $baseUrl;

    public function __construct($token, $baseUrl = 'https://thedncproject.org')
    {
        $this->token = $token;
        $this->baseUrl = rtrim($baseUrl, '/');
    }

    public function scrubNumbers(array $numbers, array $scrubTypes = array('dnc'))
    {
        $response = $this->postJson('/api/v2/scrubs', array(
            'numbers' => $numbers,
            'scrub_type' => $scrubTypes,
        ));

        return $this->decode($response, 'DNC API');
    }

    public function uploadFile($filePath, array $scrubTypes = array('dnc'), array $columns = array(0), $webhookUrl = '')
    {
        $payload = array('file' => '@' . $filePath);

        foreach ($scrubTypes as $index => $scrubType) {
            $payload['scrub_type[' . $index . ']'] = $scrubType;
        }

        foreach ($columns as $index => $column) {
            $payload['scrub_column[' . $index . ']'] = $column;
        }

        if ($webhookUrl !== '') {
            $payload['webhook_url'] = $webhookUrl;
        }

        $response = $this->postMultipart('/api/v2/scrub/file', $payload);

        return $this->decode($response, 'DNC file API');
    }

    private function postJson($path, array $payload)
    {
        $ch = curl_init($this->baseUrl . $path);

        curl_setopt_array($ch, array(
            CURLOPT_POST => true,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => array(
                'Authorization: Bearer ' . $this->token,
                'Accept: application/json',
                'Content-Type: application/json',
            ),
            CURLOPT_POSTFIELDS => json_encode($payload),
        ));

        return $this->readResponse($ch, 'DNC API');
    }

    private function postMultipart($path, array $payload)
    {
        $ch = curl_init($this->baseUrl . $path);

        curl_setopt_array($ch, array(
            CURLOPT_POST => true,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => array(
                'Authorization: Bearer ' . $this->token,
                'Accept: application/json',
            ),
            CURLOPT_POSTFIELDS => $payload,
        ));

        return $this->readResponse($ch, 'DNC file API');
    }

    private function readResponse($ch, $label)
    {
        $response = curl_exec($ch);
        $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
        $error = curl_error($ch);
        curl_close($ch);

        if ($response === false) {
            throw new RuntimeException($label . ' request failed: ' . $error);
        }

        if ($status >= 300) {
            throw new RuntimeException($label . ' failed ' . $status . ': ' . $response);
        }

        return $response;
    }

    private function decode($response, $label)
    {
        $decoded = json_decode($response, true);

        if (json_last_error() !== JSON_ERROR_NONE) {
            throw new RuntimeException($label . ' returned invalid JSON. Error code: ' . json_last_error());
        }

        return $decoded;
    }
}
PHP 7.0 DNC API client
<?php

final class DncProjectClient
{
    private $token;
    private $baseUrl;

    public function __construct(string $token, string $baseUrl = 'https://thedncproject.org')
    {
        $this->token = $token;
        $this->baseUrl = rtrim($baseUrl, '/');
    }

    public function scrubNumbers(array $numbers, array $scrubTypes = ['dnc']): array
    {
        $response = $this->postJson('/api/v2/scrubs', [
            'numbers' => $numbers,
            'scrub_type' => $scrubTypes,
        ]);

        return $this->decode($response, 'DNC API');
    }

    public function uploadFile(string $filePath, array $scrubTypes = ['dnc'], array $columns = [0], string $webhookUrl = ''): array
    {
        $payload = ['file' => new CURLFile($filePath)];

        foreach ($scrubTypes as $index => $scrubType) {
            $payload["scrub_type[{$index}]"] = $scrubType;
        }

        foreach ($columns as $index => $column) {
            $payload["scrub_column[{$index}]"] = $column;
        }

        if ($webhookUrl !== '') {
            $payload['webhook_url'] = $webhookUrl;
        }

        $response = $this->postMultipart('/api/v2/scrub/file', $payload);

        return $this->decode($response, 'DNC file API');
    }

    private function postJson(string $path, array $payload): string
    {
        $ch = curl_init($this->baseUrl . $path);

        curl_setopt_array($ch, [
            CURLOPT_POST => true,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => [
                "Authorization: Bearer {$this->token}",
                'Accept: application/json',
                'Content-Type: application/json',
            ],
            CURLOPT_POSTFIELDS => json_encode($payload),
        ]);

        return $this->readResponse($ch, 'DNC API');
    }

    private function postMultipart(string $path, array $payload): string
    {
        $ch = curl_init($this->baseUrl . $path);

        curl_setopt_array($ch, [
            CURLOPT_POST => true,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => [
                "Authorization: Bearer {$this->token}",
                'Accept: application/json',
            ],
            CURLOPT_POSTFIELDS => $payload,
        ]);

        return $this->readResponse($ch, 'DNC file API');
    }

    private function readResponse($ch, string $label): string
    {
        $response = curl_exec($ch);
        $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
        $error = curl_error($ch);
        curl_close($ch);

        if ($response === false) {
            throw new RuntimeException("{$label} request failed: {$error}");
        }

        if ($status >= 300) {
            throw new RuntimeException("{$label} failed {$status}: {$response}");
        }

        return $response;
    }

    private function decode(string $response, string $label): array
    {
        $decoded = json_decode($response, true);

        if (json_last_error() !== JSON_ERROR_NONE) {
            throw new RuntimeException("{$label} returned invalid JSON. Error code: " . json_last_error());
        }

        return $decoded;
    }
}
PHP 7.4 DNC API client
<?php

final class DncProjectClient
{
    private $token;
    private $baseUrl;

    public function __construct(string $token, string $baseUrl = 'https://thedncproject.org')
    {
        $this->token = $token;
        $this->baseUrl = rtrim($baseUrl, '/');
    }

    public function scrubNumbers(array $numbers, array $scrubTypes = ['dnc']): array
    {
        $response = $this->postJson('/api/v2/scrubs', [
            'numbers' => $numbers,
            'scrub_type' => $scrubTypes,
        ]);

        return $this->decode($response, 'DNC API');
    }

    public function uploadFile(string $filePath, array $scrubTypes = ['dnc'], array $columns = [0], string $webhookUrl = ''): array
    {
        $payload = ['file' => new CURLFile($filePath)];

        foreach ($scrubTypes as $index => $scrubType) {
            $payload["scrub_type[{$index}]"] = $scrubType;
        }

        foreach ($columns as $index => $column) {
            $payload["scrub_column[{$index}]"] = $column;
        }

        if ($webhookUrl !== '') {
            $payload['webhook_url'] = $webhookUrl;
        }

        $response = $this->postMultipart('/api/v2/scrub/file', $payload);

        return $this->decode($response, 'DNC file API');
    }

    private function postJson(string $path, array $payload): string
    {
        $ch = curl_init($this->baseUrl . $path);

        curl_setopt_array($ch, [
            CURLOPT_POST => true,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => [
                "Authorization: Bearer {$this->token}",
                'Accept: application/json',
                'Content-Type: application/json',
            ],
            CURLOPT_POSTFIELDS => json_encode($payload),
        ]);

        return $this->readResponse($ch, 'DNC API');
    }

    private function postMultipart(string $path, array $payload): string
    {
        $ch = curl_init($this->baseUrl . $path);

        curl_setopt_array($ch, [
            CURLOPT_POST => true,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => [
                "Authorization: Bearer {$this->token}",
                'Accept: application/json',
            ],
            CURLOPT_POSTFIELDS => $payload,
        ]);

        return $this->readResponse($ch, 'DNC file API');
    }

    private function readResponse($ch, string $label): string
    {
        $response = curl_exec($ch);
        $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
        $error = curl_error($ch);
        curl_close($ch);

        if ($response === false) {
            throw new RuntimeException("{$label} request failed: {$error}");
        }

        if ($status >= 300) {
            throw new RuntimeException("{$label} failed {$status}: {$response}");
        }

        return $response;
    }

    private function decode(string $response, string $label): array
    {
        $decoded = json_decode($response, true);

        if (json_last_error() !== JSON_ERROR_NONE) {
            throw new RuntimeException("{$label} returned invalid JSON. Error code: " . json_last_error());
        }

        return $decoded;
    }
}
PHP 8.0 DNC API client
<?php

final class DncProjectClient
{
    private $token;
    private $baseUrl;

    public function __construct(string $token, string $baseUrl = 'https://thedncproject.org')
    {
        $this->token = $token;
        $this->baseUrl = rtrim($baseUrl, '/');
    }

    public function scrubNumbers(array $numbers, array $scrubTypes = ['dnc']): array
    {
        $response = $this->postJson('/api/v2/scrubs', [
            'numbers' => $numbers,
            'scrub_type' => $scrubTypes,
        ]);

        return $this->decode($response, 'DNC API');
    }

    public function uploadFile(string $filePath, array $scrubTypes = ['dnc'], array $columns = [0], string $webhookUrl = ''): array
    {
        $payload = ['file' => new CURLFile($filePath)];

        foreach ($scrubTypes as $index => $scrubType) {
            $payload["scrub_type[{$index}]"] = $scrubType;
        }

        foreach ($columns as $index => $column) {
            $payload["scrub_column[{$index}]"] = $column;
        }

        if ($webhookUrl !== '') {
            $payload['webhook_url'] = $webhookUrl;
        }

        $response = $this->postMultipart('/api/v2/scrub/file', $payload);

        return $this->decode($response, 'DNC file API');
    }

    private function postJson(string $path, array $payload): string
    {
        $ch = curl_init($this->baseUrl . $path);

        curl_setopt_array($ch, [
            CURLOPT_POST => true,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => [
                "Authorization: Bearer {$this->token}",
                'Accept: application/json',
                'Content-Type: application/json',
            ],
            CURLOPT_POSTFIELDS => json_encode($payload),
        ]);

        return $this->readResponse($ch, 'DNC API');
    }

    private function postMultipart(string $path, array $payload): string
    {
        $ch = curl_init($this->baseUrl . $path);

        curl_setopt_array($ch, [
            CURLOPT_POST => true,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => [
                "Authorization: Bearer {$this->token}",
                'Accept: application/json',
            ],
            CURLOPT_POSTFIELDS => $payload,
        ]);

        return $this->readResponse($ch, 'DNC file API');
    }

    private function readResponse($ch, string $label): string
    {
        $response = curl_exec($ch);
        $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
        $error = curl_error($ch);
        curl_close($ch);

        if ($response === false) {
            throw new RuntimeException("{$label} request failed: {$error}");
        }

        if ($status >= 300) {
            throw new RuntimeException("{$label} failed {$status}: {$response}");
        }

        return $response;
    }

    private function decode(string $response, string $label): array
    {
        $decoded = json_decode($response, true);

        if (json_last_error() !== JSON_ERROR_NONE) {
            throw new RuntimeException("{$label} returned invalid JSON. Error code: " . json_last_error());
        }

        return $decoded;
    }
}
PHP 8.1 DNC API client
<?php

final class DncProjectClient
{
    public function __construct(
        private readonly string $token,
        private readonly string $baseUrl = 'https://thedncproject.org',
    ) {
    }

    public function scrubNumbers(array $numbers, array $scrubTypes = ['dnc']): array
    {
        $response = $this->postJson('/api/v2/scrubs', [
            'numbers' => $numbers,
            'scrub_type' => $scrubTypes,
        ]);

        return json_decode($response, true, 512, JSON_THROW_ON_ERROR);
    }

    public function uploadFile(string $filePath, array $scrubTypes = ['dnc'], array $columns = [0], string $webhookUrl = ''): array
    {
        $payload = ['file' => new CURLFile($filePath)];

        foreach ($scrubTypes as $index => $scrubType) {
            $payload["scrub_type[{$index}]"] = $scrubType;
        }

        foreach ($columns as $index => $column) {
            $payload["scrub_column[{$index}]"] = $column;
        }

        if ($webhookUrl !== '') {
            $payload['webhook_url'] = $webhookUrl;
        }

        $response = $this->postMultipart('/api/v2/scrub/file', $payload);

        return json_decode($response, true, 512, JSON_THROW_ON_ERROR);
    }

    private function postJson(string $path, array $payload): string
    {
        $ch = curl_init(rtrim($this->baseUrl, '/') . $path);

        curl_setopt_array($ch, [
            CURLOPT_POST => true,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => [
                "Authorization: Bearer {$this->token}",
                'Accept: application/json',
                'Content-Type: application/json',
            ],
            CURLOPT_POSTFIELDS => json_encode($payload, JSON_THROW_ON_ERROR),
        ]);

        return $this->readResponse($ch, 'DNC API');
    }

    private function postMultipart(string $path, array $payload): string
    {
        $ch = curl_init(rtrim($this->baseUrl, '/') . $path);

        curl_setopt_array($ch, [
            CURLOPT_POST => true,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => [
                "Authorization: Bearer {$this->token}",
                'Accept: application/json',
            ],
            CURLOPT_POSTFIELDS => $payload,
        ]);

        return $this->readResponse($ch, 'DNC file API');
    }

    private function readResponse($ch, string $label): string
    {
        $response = curl_exec($ch);
        $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
        $error = curl_error($ch);
        curl_close($ch);

        if ($response === false) {
            throw new RuntimeException("{$label} request failed: {$error}");
        }

        if ($status >= 300) {
            throw new RuntimeException("{$label} failed {$status}: {$response}");
        }

        return $response;
    }
}
PHP 8.2 DNC API client
<?php

final class DncProjectClient
{
    public function __construct(
        private readonly string $token,
        private readonly string $baseUrl = 'https://thedncproject.org',
    ) {
    }

    public function scrubNumbers(array $numbers, array $scrubTypes = ['dnc']): array
    {
        $response = $this->postJson('/api/v2/scrubs', [
            'numbers' => $numbers,
            'scrub_type' => $scrubTypes,
        ]);

        return json_decode($response, true, 512, JSON_THROW_ON_ERROR);
    }

    public function uploadFile(string $filePath, array $scrubTypes = ['dnc'], array $columns = [0], string $webhookUrl = ''): array
    {
        $payload = ['file' => new CURLFile($filePath)];

        foreach ($scrubTypes as $index => $scrubType) {
            $payload["scrub_type[{$index}]"] = $scrubType;
        }

        foreach ($columns as $index => $column) {
            $payload["scrub_column[{$index}]"] = $column;
        }

        if ($webhookUrl !== '') {
            $payload['webhook_url'] = $webhookUrl;
        }

        $response = $this->postMultipart('/api/v2/scrub/file', $payload);

        return json_decode($response, true, 512, JSON_THROW_ON_ERROR);
    }

    private function postJson(string $path, array $payload): string
    {
        $ch = curl_init(rtrim($this->baseUrl, '/') . $path);

        curl_setopt_array($ch, [
            CURLOPT_POST => true,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => [
                "Authorization: Bearer {$this->token}",
                'Accept: application/json',
                'Content-Type: application/json',
            ],
            CURLOPT_POSTFIELDS => json_encode($payload, JSON_THROW_ON_ERROR),
        ]);

        return $this->readResponse($ch, 'DNC API');
    }

    private function postMultipart(string $path, array $payload): string
    {
        $ch = curl_init(rtrim($this->baseUrl, '/') . $path);

        curl_setopt_array($ch, [
            CURLOPT_POST => true,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => [
                "Authorization: Bearer {$this->token}",
                'Accept: application/json',
            ],
            CURLOPT_POSTFIELDS => $payload,
        ]);

        return $this->readResponse($ch, 'DNC file API');
    }

    private function readResponse($ch, string $label): string
    {
        $response = curl_exec($ch);
        $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
        $error = curl_error($ch);
        curl_close($ch);

        if ($response === false) {
            throw new RuntimeException("{$label} request failed: {$error}");
        }

        if ($status >= 300) {
            throw new RuntimeException("{$label} failed {$status}: {$response}");
        }

        return $response;
    }
}
PHP 8.3 DNC API client
<?php

final class DncProjectClient
{
    public function __construct(
        private readonly string $token,
        private readonly string $baseUrl = 'https://thedncproject.org',
    ) {
    }

    public function scrubNumbers(array $numbers, array $scrubTypes = ['dnc']): array
    {
        $response = $this->postJson('/api/v2/scrubs', [
            'numbers' => $numbers,
            'scrub_type' => $scrubTypes,
        ]);

        return json_decode($response, true, 512, JSON_THROW_ON_ERROR);
    }

    public function uploadFile(string $filePath, array $scrubTypes = ['dnc'], array $columns = [0], string $webhookUrl = ''): array
    {
        $payload = ['file' => new CURLFile($filePath)];

        foreach ($scrubTypes as $index => $scrubType) {
            $payload["scrub_type[{$index}]"] = $scrubType;
        }

        foreach ($columns as $index => $column) {
            $payload["scrub_column[{$index}]"] = $column;
        }

        if ($webhookUrl !== '') {
            $payload['webhook_url'] = $webhookUrl;
        }

        $response = $this->postMultipart('/api/v2/scrub/file', $payload);

        return json_decode($response, true, 512, JSON_THROW_ON_ERROR);
    }

    private function postJson(string $path, array $payload): string
    {
        $ch = curl_init(rtrim($this->baseUrl, '/') . $path);

        curl_setopt_array($ch, [
            CURLOPT_POST => true,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => [
                "Authorization: Bearer {$this->token}",
                'Accept: application/json',
                'Content-Type: application/json',
            ],
            CURLOPT_POSTFIELDS => json_encode($payload, JSON_THROW_ON_ERROR),
        ]);

        return $this->readResponse($ch, 'DNC API');
    }

    private function postMultipart(string $path, array $payload): string
    {
        $ch = curl_init(rtrim($this->baseUrl, '/') . $path);

        curl_setopt_array($ch, [
            CURLOPT_POST => true,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => [
                "Authorization: Bearer {$this->token}",
                'Accept: application/json',
            ],
            CURLOPT_POSTFIELDS => $payload,
        ]);

        return $this->readResponse($ch, 'DNC file API');
    }

    private function readResponse($ch, string $label): string
    {
        $response = curl_exec($ch);
        $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
        $error = curl_error($ch);
        curl_close($ch);

        if ($response === false) {
            throw new RuntimeException("{$label} request failed: {$error}");
        }

        if ($status >= 300) {
            throw new RuntimeException("{$label} failed {$status}: {$response}");
        }

        return $response;
    }
}
PHP 8.4 DNC API client
<?php

final class DncProjectClient
{
    public function __construct(
        private readonly string $token,
        private readonly string $baseUrl = 'https://thedncproject.org',
    ) {
    }

    public function scrubNumbers(array $numbers, array $scrubTypes = ['dnc']): array
    {
        $response = $this->postJson('/api/v2/scrubs', [
            'numbers' => $numbers,
            'scrub_type' => $scrubTypes,
        ]);

        return json_decode($response, true, 512, JSON_THROW_ON_ERROR);
    }

    public function uploadFile(string $filePath, array $scrubTypes = ['dnc'], array $columns = [0], string $webhookUrl = ''): array
    {
        $payload = ['file' => new CURLFile($filePath)];

        foreach ($scrubTypes as $index => $scrubType) {
            $payload["scrub_type[{$index}]"] = $scrubType;
        }

        foreach ($columns as $index => $column) {
            $payload["scrub_column[{$index}]"] = $column;
        }

        if ($webhookUrl !== '') {
            $payload['webhook_url'] = $webhookUrl;
        }

        $response = $this->postMultipart('/api/v2/scrub/file', $payload);

        return json_decode($response, true, 512, JSON_THROW_ON_ERROR);
    }

    private function postJson(string $path, array $payload): string
    {
        $ch = curl_init(rtrim($this->baseUrl, '/') . $path);

        curl_setopt_array($ch, [
            CURLOPT_POST => true,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => [
                "Authorization: Bearer {$this->token}",
                'Accept: application/json',
                'Content-Type: application/json',
            ],
            CURLOPT_POSTFIELDS => json_encode($payload, JSON_THROW_ON_ERROR),
        ]);

        return $this->readResponse($ch, 'DNC API');
    }

    private function postMultipart(string $path, array $payload): string
    {
        $ch = curl_init(rtrim($this->baseUrl, '/') . $path);

        curl_setopt_array($ch, [
            CURLOPT_POST => true,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => [
                "Authorization: Bearer {$this->token}",
                'Accept: application/json',
            ],
            CURLOPT_POSTFIELDS => $payload,
        ]);

        return $this->readResponse($ch, 'DNC file API');
    }

    private function readResponse($ch, string $label): string
    {
        $response = curl_exec($ch);
        $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
        $error = curl_error($ch);
        curl_close($ch);

        if ($response === false) {
            throw new RuntimeException("{$label} request failed: {$error}");
        }

        if ($status >= 300) {
            throw new RuntimeException("{$label} failed {$status}: {$response}");
        }

        return $response;
    }
}
PHP 8.5 DNC API client
<?php

final class DncProjectClient
{
    public function __construct(
        private readonly string $token,
        private readonly string $baseUrl = 'https://thedncproject.org',
    ) {
    }

    public function scrubNumbers(array $numbers, array $scrubTypes = ['dnc']): array
    {
        $response = $this->postJson('/api/v2/scrubs', [
            'numbers' => $numbers,
            'scrub_type' => $scrubTypes,
        ]);

        return json_decode($response, true, 512, JSON_THROW_ON_ERROR);
    }

    public function uploadFile(string $filePath, array $scrubTypes = ['dnc'], array $columns = [0], string $webhookUrl = ''): array
    {
        $payload = ['file' => new CURLFile($filePath)];

        foreach ($scrubTypes as $index => $scrubType) {
            $payload["scrub_type[{$index}]"] = $scrubType;
        }

        foreach ($columns as $index => $column) {
            $payload["scrub_column[{$index}]"] = $column;
        }

        if ($webhookUrl !== '') {
            $payload['webhook_url'] = $webhookUrl;
        }

        $response = $this->postMultipart('/api/v2/scrub/file', $payload);

        return json_decode($response, true, 512, JSON_THROW_ON_ERROR);
    }

    private function postJson(string $path, array $payload): string
    {
        $ch = curl_init(rtrim($this->baseUrl, '/') . $path);

        curl_setopt_array($ch, [
            CURLOPT_POST => true,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => [
                "Authorization: Bearer {$this->token}",
                'Accept: application/json',
                'Content-Type: application/json',
            ],
            CURLOPT_POSTFIELDS => json_encode($payload, JSON_THROW_ON_ERROR),
        ]);

        return $this->readResponse($ch, 'DNC API');
    }

    private function postMultipart(string $path, array $payload): string
    {
        $ch = curl_init(rtrim($this->baseUrl, '/') . $path);

        curl_setopt_array($ch, [
            CURLOPT_POST => true,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => [
                "Authorization: Bearer {$this->token}",
                'Accept: application/json',
            ],
            CURLOPT_POSTFIELDS => $payload,
        ]);

        return $this->readResponse($ch, 'DNC file API');
    }

    private function readResponse($ch, string $label): string
    {
        $response = curl_exec($ch);
        $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
        $error = curl_error($ch);
        curl_close($ch);

        if ($response === false) {
            throw new RuntimeException("{$label} request failed: {$error}");
        }

        if ($status >= 300) {
            throw new RuntimeException("{$label} failed {$status}: {$response}");
        }

        return $response;
    }
}
ES5 DNC API client
function DncProjectClient(token, baseUrl) {
    this.token = token;
    this.baseUrl = (baseUrl || 'https://thedncproject.org').replace(/\/$/, '');
}

DncProjectClient.prototype.scrubNumbers = function (numbers, scrubTypes, callback) {
    this.requestJson('/api/v2/scrubs', {
        numbers: numbers,
        scrub_type: scrubTypes || ['dnc'],
    }, callback);
};

DncProjectClient.prototype.uploadFile = function (file, options, callback) {
    options = options || {};

    var formData = new FormData();
    var scrubTypes = options.scrubTypes || ['dnc'];
    var columns = options.columns || [0];

    formData.append('file', file);

    scrubTypes.forEach(function (scrubType) {
        formData.append('scrub_type[]', scrubType);
    });

    columns.forEach(function (column) {
        formData.append('scrub_column[]', String(column));
    });

    if (options.webhookUrl) {
        formData.append('webhook_url', options.webhookUrl);
    }

    this.requestForm('/api/v2/scrub/file', formData, callback);
};

DncProjectClient.prototype.requestJson = function (path, payload, callback) {
    this.request(path, JSON.stringify(payload), {
        'Content-Type': 'application/json',
    }, callback);
};

DncProjectClient.prototype.requestForm = function (path, body, callback) {
    this.request(path, body, {}, callback);
};

DncProjectClient.prototype.request = function (path, body, headers, callback) {
    var xhr = new XMLHttpRequest();

    xhr.open('POST', this.baseUrl + path, true);
    xhr.setRequestHeader('Authorization', 'Bearer ' + this.token);
    xhr.setRequestHeader('Accept', 'application/json');

    Object.keys(headers).forEach(function (name) {
        xhr.setRequestHeader(name, headers[name]);
    });

    xhr.onreadystatechange = function () {
        if (xhr.readyState !== 4) {
            return;
        }

        if (xhr.status >= 300) {
            callback(new Error('DNC API failed ' + xhr.status + ': ' + xhr.responseText));
            return;
        }

        callback(null, JSON.parse(xhr.responseText));
    };

    xhr.send(body);
};
ES2017+ DNC API client
class DncProjectClient {
    constructor(token, baseUrl = 'https://thedncproject.org') {
        this.token = token;
        this.baseUrl = baseUrl.replace(/\/$/, '');
    }

    async scrubNumbers(numbers, scrubTypes = ['dnc']) {
        return this.postJson('/api/v2/scrubs', {
            numbers,
            scrub_type: scrubTypes,
        });
    }

    async uploadFile(file, options = {}) {
        const formData = new FormData();
        const scrubTypes = options.scrubTypes || ['dnc'];
        const columns = options.columns || [0];

        formData.append('file', file);
        scrubTypes.forEach((scrubType) => formData.append('scrub_type[]', scrubType));
        columns.forEach((column) => formData.append('scrub_column[]', String(column)));

        if (options.webhookUrl) {
            formData.append('webhook_url', options.webhookUrl);
        }

        return this.postForm('/api/v2/scrub/file', formData);
    }

    async postJson(path, payload) {
        return this.request(path, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify(payload),
        });
    }

    async postForm(path, body) {
        return this.request(path, {
            method: 'POST',
            body,
        });
    }

    async request(path, options) {
        const requestOptions = Object.assign({}, options);
        requestOptions.headers = Object.assign({
            Authorization: `Bearer ${this.token}`,
            Accept: 'application/json',
        }, options.headers || {});

        const response = await fetch(`${this.baseUrl}${path}`, requestOptions);

        const body = await response.text();

        if (!response.ok) {
            throw new Error(`DNC API failed ${response.status}: ${body}`);
        }

        return JSON.parse(body);
    }
}
Python 2.7 DNC API client
import requests


class DncProjectClient:
    def __init__(self, token, base_url='https://thedncproject.org'):
        self.token = token
        self.base_url = base_url.rstrip('/')

    def scrub_numbers(self, numbers, scrub_types=('dnc',), timeout=30):
        return self.post_json('/api/v2/scrubs', {
            'numbers': list(numbers),
            'scrub_type': list(scrub_types),
        }, timeout=timeout)

    def upload_file(self, file_path, scrub_types=('dnc',), columns=(0,), webhook_url=None, timeout=120):
        data = []
        data.extend(('scrub_type[]', scrub_type) for scrub_type in scrub_types)
        data.extend(('scrub_column[]', str(column)) for column in columns)

        if webhook_url:
            data.append(('webhook_url', webhook_url))

        with open(file_path, 'rb') as file:
            response = requests.post(
                '{}{}'.format(self.base_url, '/api/v2/scrub/file'),
                headers=self.headers(),
                files={'file': (file_path, file, 'text/csv')},
                data=data,
                timeout=timeout,
            )

        response.raise_for_status()
        return response.json()

    def post_json(self, path, payload, timeout=30):
        response = requests.post(
            '{}{}'.format(self.base_url, path),
            headers=dict(self.headers(), **{'Content-Type': 'application/json'}),
            json=payload,
            timeout=timeout,
        )

        response.raise_for_status()
        return response.json()

    def headers(self):
        return {
            'Authorization': 'Bearer {}'.format(self.token),
            'Accept': 'application/json',
        }
Python 3.6 DNC API client
import requests


class DncProjectClient:
    def __init__(self, token, base_url='https://thedncproject.org'):
        self.token = token
        self.base_url = base_url.rstrip('/')

    def scrub_numbers(self, numbers, scrub_types=('dnc',), timeout=30):
        return self.post_json('/api/v2/scrubs', {
            'numbers': list(numbers),
            'scrub_type': list(scrub_types),
        }, timeout=timeout)

    def upload_file(self, file_path, scrub_types=('dnc',), columns=(0,), webhook_url=None, timeout=120):
        data = []
        data.extend(('scrub_type[]', scrub_type) for scrub_type in scrub_types)
        data.extend(('scrub_column[]', str(column)) for column in columns)

        if webhook_url:
            data.append(('webhook_url', webhook_url))

        with open(file_path, 'rb') as file:
            response = requests.post(
                '{}{}'.format(self.base_url, '/api/v2/scrub/file'),
                headers=self.headers(),
                files={'file': (file_path, file, 'text/csv')},
                data=data,
                timeout=timeout,
            )

        response.raise_for_status()
        return response.json()

    def post_json(self, path, payload, timeout=30):
        response = requests.post(
            '{}{}'.format(self.base_url, path),
            headers=dict(self.headers(), **{'Content-Type': 'application/json'}),
            json=payload,
            timeout=timeout,
        )

        response.raise_for_status()
        return response.json()

    def headers(self):
        return {
            'Authorization': 'Bearer {}'.format(self.token),
            'Accept': 'application/json',
        }
Python 3.8 DNC API client
import requests


class DncProjectClient:
    def __init__(self, token, base_url='https://thedncproject.org'):
        self.token = token
        self.base_url = base_url.rstrip('/')

    def scrub_numbers(self, numbers, scrub_types=('dnc',), timeout=30):
        return self.post_json('/api/v2/scrubs', {
            'numbers': list(numbers),
            'scrub_type': list(scrub_types),
        }, timeout=timeout)

    def upload_file(self, file_path, scrub_types=('dnc',), columns=(0,), webhook_url=None, timeout=120):
        data = []
        data.extend(('scrub_type[]', scrub_type) for scrub_type in scrub_types)
        data.extend(('scrub_column[]', str(column)) for column in columns)

        if webhook_url:
            data.append(('webhook_url', webhook_url))

        with open(file_path, 'rb') as file:
            response = requests.post(
                '{}{}'.format(self.base_url, '/api/v2/scrub/file'),
                headers=self.headers(),
                files={'file': (file_path, file, 'text/csv')},
                data=data,
                timeout=timeout,
            )

        response.raise_for_status()
        return response.json()

    def post_json(self, path, payload, timeout=30):
        response = requests.post(
            '{}{}'.format(self.base_url, path),
            headers=dict(self.headers(), **{'Content-Type': 'application/json'}),
            json=payload,
            timeout=timeout,
        )

        response.raise_for_status()
        return response.json()

    def headers(self):
        return {
            'Authorization': 'Bearer {}'.format(self.token),
            'Accept': 'application/json',
        }
Python 3.10+ DNC API client
import requests


class DncProjectClient:
    def __init__(self, token, base_url='https://thedncproject.org'):
        self.token = token
        self.base_url = base_url.rstrip('/')

    def scrub_numbers(self, numbers, scrub_types=('dnc',), timeout=30):
        return self.post_json('/api/v2/scrubs', {
            'numbers': list(numbers),
            'scrub_type': list(scrub_types),
        }, timeout=timeout)

    def upload_file(self, file_path, scrub_types=('dnc',), columns=(0,), webhook_url=None, timeout=120):
        data = []
        data.extend(('scrub_type[]', scrub_type) for scrub_type in scrub_types)
        data.extend(('scrub_column[]', str(column)) for column in columns)

        if webhook_url:
            data.append(('webhook_url', webhook_url))

        with open(file_path, 'rb') as file:
            response = requests.post(
                '{}{}'.format(self.base_url, '/api/v2/scrub/file'),
                headers=self.headers(),
                files={'file': (file_path, file, 'text/csv')},
                data=data,
                timeout=timeout,
            )

        response.raise_for_status()
        return response.json()

    def post_json(self, path, payload, timeout=30):
        response = requests.post(
            '{}{}'.format(self.base_url, path),
            headers=dict(self.headers(), **{'Content-Type': 'application/json'}),
            json=payload,
            timeout=timeout,
        )

        response.raise_for_status()
        return response.json()

    def headers(self):
        return {
            'Authorization': 'Bearer {}'.format(self.token),
            'Accept': 'application/json',
        }
Go 1.13 DNC API client
package dncproject

import (
    "bytes"
    "context"
    "encoding/json"
    "fmt"
    "io"
    "io/ioutil"
    "mime/multipart"
    "net/http"
    "os"
    "path/filepath"
    "strconv"
    "strings"
)

type Client struct {
    Token   string
    BaseURL string
    HTTP    *http.Client
}

func (client Client) ScrubNumbers(ctx context.Context, numbers []string, scrubTypes []string) ([]byte, error) {
    payload := map[string]interface{}{
        "numbers": numbers,
        "scrub_type": scrubTypes,
    }

    body, err := json.Marshal(payload)
    if err != nil {
        return nil, err
    }

    return client.post(ctx, "/api/v2/scrubs", "application/json", bytes.NewReader(body))
}

func (client Client) UploadFile(ctx context.Context, filePath string, scrubTypes []string, columns []int, webhookURL string) ([]byte, error) {
    var body bytes.Buffer
    writer := multipart.NewWriter(&body)

    file, err := os.Open(filePath)
    if err != nil {
        return nil, err
    }
    defer file.Close()

    part, err := writer.CreateFormFile("file", filepath.Base(filePath))
    if err != nil {
        return nil, err
    }

    if _, err := io.Copy(part, file); err != nil {
        return nil, err
    }

    for _, scrubType := range scrubTypes {
        if err := writer.WriteField("scrub_type[]", scrubType); err != nil {
            return nil, err
        }
    }

    for _, column := range columns {
        if err := writer.WriteField("scrub_column[]", strconv.Itoa(column)); err != nil {
            return nil, err
        }
    }

    if webhookURL != "" {
        if err := writer.WriteField("webhook_url", webhookURL); err != nil {
            return nil, err
        }
    }

    if err := writer.Close(); err != nil {
        return nil, err
    }

    return client.post(ctx, "/api/v2/scrub/file", writer.FormDataContentType(), &body)
}

func (client Client) post(ctx context.Context, path string, contentType string, body io.Reader) ([]byte, error) {
    endpoint := strings.TrimRight(client.BaseURL, "/") + path
    request, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, body)
    if err != nil {
        return nil, err
    }

    request.Header.Set("Authorization", "Bearer "+client.Token)
    request.Header.Set("Accept", "application/json")
    request.Header.Set("Content-Type", contentType)

    httpClient := client.HTTP
    if httpClient == nil {
        httpClient = http.DefaultClient
    }

    response, err := httpClient.Do(request)
    if err != nil {
        return nil, err
    }
    defer response.Body.Close()

    responseBody, err := ioutil.ReadAll(response.Body)
    if err != nil {
        return nil, err
    }

    if response.StatusCode >= 300 {
        return nil, fmt.Errorf("DNC API failed %d: %s", response.StatusCode, string(responseBody))
    }

    return responseBody, nil
}
Go 1.16+ DNC API client
package dncproject

import (
    "bytes"
    "context"
    "encoding/json"
    "fmt"
    "io"
    "mime/multipart"
    "net/http"
    "os"
    "path/filepath"
    "strconv"
    "strings"
)

type Client struct {
    Token   string
    BaseURL string
    HTTP    *http.Client
}

func (client Client) ScrubNumbers(ctx context.Context, numbers []string, scrubTypes []string) ([]byte, error) {
    payload := map[string]interface{}{
        "numbers": numbers,
        "scrub_type": scrubTypes,
    }

    body, err := json.Marshal(payload)
    if err != nil {
        return nil, err
    }

    return client.post(ctx, "/api/v2/scrubs", "application/json", bytes.NewReader(body))
}

func (client Client) UploadFile(ctx context.Context, filePath string, scrubTypes []string, columns []int, webhookURL string) ([]byte, error) {
    var body bytes.Buffer
    writer := multipart.NewWriter(&body)

    file, err := os.Open(filePath)
    if err != nil {
        return nil, err
    }
    defer file.Close()

    part, err := writer.CreateFormFile("file", filepath.Base(filePath))
    if err != nil {
        return nil, err
    }

    if _, err := io.Copy(part, file); err != nil {
        return nil, err
    }

    for _, scrubType := range scrubTypes {
        if err := writer.WriteField("scrub_type[]", scrubType); err != nil {
            return nil, err
        }
    }

    for _, column := range columns {
        if err := writer.WriteField("scrub_column[]", strconv.Itoa(column)); err != nil {
            return nil, err
        }
    }

    if webhookURL != "" {
        if err := writer.WriteField("webhook_url", webhookURL); err != nil {
            return nil, err
        }
    }

    if err := writer.Close(); err != nil {
        return nil, err
    }

    return client.post(ctx, "/api/v2/scrub/file", writer.FormDataContentType(), &body)
}

func (client Client) post(ctx context.Context, path string, contentType string, body io.Reader) ([]byte, error) {
    endpoint := strings.TrimRight(client.BaseURL, "/") + path
    request, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, body)
    if err != nil {
        return nil, err
    }

    request.Header.Set("Authorization", "Bearer "+client.Token)
    request.Header.Set("Accept", "application/json")
    request.Header.Set("Content-Type", contentType)

    httpClient := client.HTTP
    if httpClient == nil {
        httpClient = http.DefaultClient
    }

    response, err := httpClient.Do(request)
    if err != nil {
        return nil, err
    }
    defer response.Body.Close()

    responseBody, err := io.ReadAll(response.Body)
    if err != nil {
        return nil, err
    }

    if response.StatusCode >= 300 {
        return nil, fmt.Errorf("DNC API failed %d: %s", response.StatusCode, string(responseBody))
    }

    return responseBody, nil
}
Go stable DNC API client
package dncproject

import (
    "bytes"
    "context"
    "encoding/json"
    "fmt"
    "io"
    "mime/multipart"
    "net/http"
    "os"
    "path/filepath"
    "strconv"
    "strings"
)

type Client struct {
    Token   string
    BaseURL string
    HTTP    *http.Client
}

func (client Client) ScrubNumbers(ctx context.Context, numbers []string, scrubTypes []string) ([]byte, error) {
    payload := map[string]interface{}{
        "numbers": numbers,
        "scrub_type": scrubTypes,
    }

    body, err := json.Marshal(payload)
    if err != nil {
        return nil, err
    }

    return client.post(ctx, "/api/v2/scrubs", "application/json", bytes.NewReader(body))
}

func (client Client) UploadFile(ctx context.Context, filePath string, scrubTypes []string, columns []int, webhookURL string) ([]byte, error) {
    var body bytes.Buffer
    writer := multipart.NewWriter(&body)

    file, err := os.Open(filePath)
    if err != nil {
        return nil, err
    }
    defer file.Close()

    part, err := writer.CreateFormFile("file", filepath.Base(filePath))
    if err != nil {
        return nil, err
    }

    if _, err := io.Copy(part, file); err != nil {
        return nil, err
    }

    for _, scrubType := range scrubTypes {
        if err := writer.WriteField("scrub_type[]", scrubType); err != nil {
            return nil, err
        }
    }

    for _, column := range columns {
        if err := writer.WriteField("scrub_column[]", strconv.Itoa(column)); err != nil {
            return nil, err
        }
    }

    if webhookURL != "" {
        if err := writer.WriteField("webhook_url", webhookURL); err != nil {
            return nil, err
        }
    }

    if err := writer.Close(); err != nil {
        return nil, err
    }

    return client.post(ctx, "/api/v2/scrub/file", writer.FormDataContentType(), &body)
}

func (client Client) post(ctx context.Context, path string, contentType string, body io.Reader) ([]byte, error) {
    endpoint := strings.TrimRight(client.BaseURL, "/") + path
    request, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, body)
    if err != nil {
        return nil, err
    }

    request.Header.Set("Authorization", "Bearer "+client.Token)
    request.Header.Set("Accept", "application/json")
    request.Header.Set("Content-Type", contentType)

    httpClient := client.HTTP
    if httpClient == nil {
        httpClient = http.DefaultClient
    }

    response, err := httpClient.Do(request)
    if err != nil {
        return nil, err
    }
    defer response.Body.Close()

    responseBody, err := io.ReadAll(response.Body)
    if err != nil {
        return nil, err
    }

    if response.StatusCode >= 300 {
        return nil, fmt.Errorf("DNC API failed %d: %s", response.StatusCode, string(responseBody))
    }

    return responseBody, nil
}
Rust 1.56+ DNC API client
use reqwest::blocking::{multipart, Client};
use serde_json::{json, Value};
use std::fs::File;
use std::path::Path;

pub struct DncProjectClient {
    token: String,
    base_url: String,
    http: Client,
}

impl DncProjectClient {
    pub fn new(token: impl Into<String>) -> Self {
        Self {
            token: token.into(),
            base_url: "https://thedncproject.org".to_string(),
            http: Client::new(),
        }
    }

    pub fn scrub_numbers(&self, numbers: &[&str], scrub_types: &[&str]) -> Result<Value, Box<dyn std::error::Error>> {
        self.post_json("/api/v2/scrubs", json!({
            "numbers": numbers,
            "scrub_type": scrub_types,
        }))
    }

    pub fn upload_file(
        &self,
        file_path: &str,
        scrub_types: &[&str],
        columns: &[u8],
        webhook_url: Option<&str>,
    ) -> Result<Value, Box<dyn std::error::Error>> {
        let path = Path::new(file_path);
        let file = File::open(path)?;
        let file_part = multipart::Part::reader(file)
            .file_name(path.file_name().unwrap().to_string_lossy().to_string())
            .mime_str("text/csv")?;

        let mut form = multipart::Form::new().part("file", file_part);

        for scrub_type in scrub_types {
            form = form.text("scrub_type[]", scrub_type.to_string());
        }

        for column in columns {
            form = form.text("scrub_column[]", column.to_string());
        }

        if let Some(url) = webhook_url {
            form = form.text("webhook_url", url.to_string());
        }

        self.post_multipart("/api/v2/scrub/file", form)
    }

    fn post_json(&self, path: &str, payload: Value) -> Result<Value, Box<dyn std::error::Error>> {
        let response = self.http
            .post(format!("{}{}", self.base_url.trim_end_matches('/'), path))
            .bearer_auth(&self.token)
            .header("Accept", "application/json")
            .json(&payload)
            .send()?;

        Self::read_response(response)
    }

    fn post_multipart(&self, path: &str, form: multipart::Form) -> Result<Value, Box<dyn std::error::Error>> {
        let response = self.http
            .post(format!("{}{}", self.base_url.trim_end_matches('/'), path))
            .bearer_auth(&self.token)
            .header("Accept", "application/json")
            .multipart(form)
            .send()?;

        Self::read_response(response)
    }

    fn read_response(response: reqwest::blocking::Response) -> Result<Value, Box<dyn std::error::Error>> {
        let status = response.status();
        let body = response.text()?;

        if !status.is_success() {
            return Err(format!("DNC API failed {}: {}", status.as_u16(), body).into());
        }

        Ok(serde_json::from_str(&body)?)
    }
}
Rust 1.70+ DNC API client
use reqwest::blocking::{multipart, Client};
use serde_json::{json, Value};
use std::fs::File;
use std::path::Path;

pub struct DncProjectClient {
    token: String,
    base_url: String,
    http: Client,
}

impl DncProjectClient {
    pub fn new(token: impl Into<String>) -> Self {
        Self {
            token: token.into(),
            base_url: "https://thedncproject.org".to_string(),
            http: Client::new(),
        }
    }

    pub fn scrub_numbers(&self, numbers: &[&str], scrub_types: &[&str]) -> Result<Value, Box<dyn std::error::Error>> {
        self.post_json("/api/v2/scrubs", json!({
            "numbers": numbers,
            "scrub_type": scrub_types,
        }))
    }

    pub fn upload_file(
        &self,
        file_path: &str,
        scrub_types: &[&str],
        columns: &[u8],
        webhook_url: Option<&str>,
    ) -> Result<Value, Box<dyn std::error::Error>> {
        let path = Path::new(file_path);
        let file = File::open(path)?;
        let file_part = multipart::Part::reader(file)
            .file_name(path.file_name().unwrap().to_string_lossy().to_string())
            .mime_str("text/csv")?;

        let mut form = multipart::Form::new().part("file", file_part);

        for scrub_type in scrub_types {
            form = form.text("scrub_type[]", scrub_type.to_string());
        }

        for column in columns {
            form = form.text("scrub_column[]", column.to_string());
        }

        if let Some(url) = webhook_url {
            form = form.text("webhook_url", url.to_string());
        }

        self.post_multipart("/api/v2/scrub/file", form)
    }

    fn post_json(&self, path: &str, payload: Value) -> Result<Value, Box<dyn std::error::Error>> {
        let response = self.http
            .post(format!("{}{}", self.base_url.trim_end_matches('/'), path))
            .bearer_auth(&self.token)
            .header("Accept", "application/json")
            .json(&payload)
            .send()?;

        Self::read_response(response)
    }

    fn post_multipart(&self, path: &str, form: multipart::Form) -> Result<Value, Box<dyn std::error::Error>> {
        let response = self.http
            .post(format!("{}{}", self.base_url.trim_end_matches('/'), path))
            .bearer_auth(&self.token)
            .header("Accept", "application/json")
            .multipart(form)
            .send()?;

        Self::read_response(response)
    }

    fn read_response(response: reqwest::blocking::Response) -> Result<Value, Box<dyn std::error::Error>> {
        let status = response.status();
        let body = response.text()?;

        if !status.is_success() {
            return Err(format!("DNC API failed {}: {}", status.as_u16(), body).into());
        }

        Ok(serde_json::from_str(&body)?)
    }
}
Rust stable DNC API client
use reqwest::blocking::{multipart, Client};
use serde_json::{json, Value};
use std::fs::File;
use std::path::Path;

pub struct DncProjectClient {
    token: String,
    base_url: String,
    http: Client,
}

impl DncProjectClient {
    pub fn new(token: impl Into<String>) -> Self {
        Self {
            token: token.into(),
            base_url: "https://thedncproject.org".to_string(),
            http: Client::new(),
        }
    }

    pub fn scrub_numbers(&self, numbers: &[&str], scrub_types: &[&str]) -> Result<Value, Box<dyn std::error::Error>> {
        self.post_json("/api/v2/scrubs", json!({
            "numbers": numbers,
            "scrub_type": scrub_types,
        }))
    }

    pub fn upload_file(
        &self,
        file_path: &str,
        scrub_types: &[&str],
        columns: &[u8],
        webhook_url: Option<&str>,
    ) -> Result<Value, Box<dyn std::error::Error>> {
        let path = Path::new(file_path);
        let file = File::open(path)?;
        let file_part = multipart::Part::reader(file)
            .file_name(path.file_name().unwrap().to_string_lossy().to_string())
            .mime_str("text/csv")?;

        let mut form = multipart::Form::new().part("file", file_part);

        for scrub_type in scrub_types {
            form = form.text("scrub_type[]", scrub_type.to_string());
        }

        for column in columns {
            form = form.text("scrub_column[]", column.to_string());
        }

        if let Some(url) = webhook_url {
            form = form.text("webhook_url", url.to_string());
        }

        self.post_multipart("/api/v2/scrub/file", form)
    }

    fn post_json(&self, path: &str, payload: Value) -> Result<Value, Box<dyn std::error::Error>> {
        let response = self.http
            .post(format!("{}{}", self.base_url.trim_end_matches('/'), path))
            .bearer_auth(&self.token)
            .header("Accept", "application/json")
            .json(&payload)
            .send()?;

        Self::read_response(response)
    }

    fn post_multipart(&self, path: &str, form: multipart::Form) -> Result<Value, Box<dyn std::error::Error>> {
        let response = self.http
            .post(format!("{}{}", self.base_url.trim_end_matches('/'), path))
            .bearer_auth(&self.token)
            .header("Accept", "application/json")
            .multipart(form)
            .send()?;

        Self::read_response(response)
    }

    fn read_response(response: reqwest::blocking::Response) -> Result<Value, Box<dyn std::error::Error>> {
        let status = response.status();
        let body = response.text()?;

        if !status.is_success() {
            return Err(format!("DNC API failed {}: {}", status.as_u16(), body).into());
        }

        Ok(serde_json::from_str(&body)?)
    }
}
Ruby 2.7 DNC API client
require 'json'
require 'net/http'
require 'securerandom'
require 'uri'

class DncProjectClient
  def initialize(token, base_url = 'https://thedncproject.org')
    @token = token
    @base_url = base_url.sub(%r{/$}, '')
  end

  def scrub_numbers(numbers, scrub_types = ['dnc'])
    post_json('/api/v2/scrubs', {
      numbers: numbers,
      scrub_type: scrub_types
    })
  end

  def upload_file(file_path, scrub_types: ['dnc'], columns: [0], webhook_url: nil)
    boundary = "----DNC#{SecureRandom.hex(12)}"
    body = +''

    add_file(body, boundary, 'file', file_path, 'text/csv')
    scrub_types.each { |scrub_type| add_field(body, boundary, 'scrub_type[]', scrub_type) }
    columns.each { |column| add_field(body, boundary, 'scrub_column[]', column) }
    add_field(body, boundary, 'webhook_url', webhook_url) if webhook_url
    body << "--#{boundary}--\r\n"

    request = Net::HTTP::Post.new(uri('/api/v2/scrub/file'))
    request['Authorization'] = "Bearer #{@token}"
    request['Accept'] = 'application/json'
    request['Content-Type'] = "multipart/form-data; boundary=#{boundary}"
    request.body = body

    parse_response(request)
  end

  private

  def post_json(path, payload)
    request = Net::HTTP::Post.new(uri(path))
    request['Authorization'] = "Bearer #{@token}"
    request['Accept'] = 'application/json'
    request['Content-Type'] = 'application/json'
    request.body = payload.to_json

    parse_response(request)
  end

  def parse_response(request)
    response = Net::HTTP.start(request.uri.hostname, request.uri.port, use_ssl: true) do |http|
      http.request(request)
    end

    unless response.is_a?(Net::HTTPSuccess)
      raise "DNC API failed #{response.code}: #{response.body}"
    end

    JSON.parse(response.body)
  end

  def uri(path)
    URI("#{@base_url}#{path}")
  end

  def add_field(body, boundary, name, value)
    body << "--#{boundary}\r\n"
    body << "Content-Disposition: form-data; name=\"#{name}\"\r\n\r\n"
    body << "#{value}\r\n"
  end

  def add_file(body, boundary, name, path, content_type)
    body << "--#{boundary}\r\n"
    body << "Content-Disposition: form-data; name=\"#{name}\"; filename=\"#{File.basename(path)}\"\r\n"
    body << "Content-Type: #{content_type}\r\n\r\n"
    body << File.binread(path)
    body << "\r\n"
  end
end
Ruby 3.0 DNC API client
require 'json'
require 'net/http'
require 'securerandom'
require 'uri'

class DncProjectClient
  def initialize(token, base_url = 'https://thedncproject.org')
    @token = token
    @base_url = base_url.sub(%r{/$}, '')
  end

  def scrub_numbers(numbers, scrub_types = ['dnc'])
    post_json('/api/v2/scrubs', {
      numbers: numbers,
      scrub_type: scrub_types
    })
  end

  def upload_file(file_path, scrub_types: ['dnc'], columns: [0], webhook_url: nil)
    boundary = "----DNC#{SecureRandom.hex(12)}"
    body = +''

    add_file(body, boundary, 'file', file_path, 'text/csv')
    scrub_types.each { |scrub_type| add_field(body, boundary, 'scrub_type[]', scrub_type) }
    columns.each { |column| add_field(body, boundary, 'scrub_column[]', column) }
    add_field(body, boundary, 'webhook_url', webhook_url) if webhook_url
    body << "--#{boundary}--\r\n"

    request = Net::HTTP::Post.new(uri('/api/v2/scrub/file'))
    request['Authorization'] = "Bearer #{@token}"
    request['Accept'] = 'application/json'
    request['Content-Type'] = "multipart/form-data; boundary=#{boundary}"
    request.body = body

    parse_response(request)
  end

  private

  def post_json(path, payload)
    request = Net::HTTP::Post.new(uri(path))
    request['Authorization'] = "Bearer #{@token}"
    request['Accept'] = 'application/json'
    request['Content-Type'] = 'application/json'
    request.body = payload.to_json

    parse_response(request)
  end

  def parse_response(request)
    response = Net::HTTP.start(request.uri.hostname, request.uri.port, use_ssl: true) do |http|
      http.request(request)
    end

    unless response.is_a?(Net::HTTPSuccess)
      raise "DNC API failed #{response.code}: #{response.body}"
    end

    JSON.parse(response.body)
  end

  def uri(path)
    URI("#{@base_url}#{path}")
  end

  def add_field(body, boundary, name, value)
    body << "--#{boundary}\r\n"
    body << "Content-Disposition: form-data; name=\"#{name}\"\r\n\r\n"
    body << "#{value}\r\n"
  end

  def add_file(body, boundary, name, path, content_type)
    body << "--#{boundary}\r\n"
    body << "Content-Disposition: form-data; name=\"#{name}\"; filename=\"#{File.basename(path)}\"\r\n"
    body << "Content-Type: #{content_type}\r\n\r\n"
    body << File.binread(path)
    body << "\r\n"
  end
end
Ruby 3.1+ DNC API client
require 'json'
require 'net/http'
require 'securerandom'
require 'uri'

class DncProjectClient
  def initialize(token, base_url = 'https://thedncproject.org')
    @token = token
    @base_url = base_url.sub(%r{/$}, '')
  end

  def scrub_numbers(numbers, scrub_types = ['dnc'])
    post_json('/api/v2/scrubs', {
      numbers: numbers,
      scrub_type: scrub_types
    })
  end

  def upload_file(file_path, scrub_types: ['dnc'], columns: [0], webhook_url: nil)
    boundary = "----DNC#{SecureRandom.hex(12)}"
    body = +''

    add_file(body, boundary, 'file', file_path, 'text/csv')
    scrub_types.each { |scrub_type| add_field(body, boundary, 'scrub_type[]', scrub_type) }
    columns.each { |column| add_field(body, boundary, 'scrub_column[]', column) }
    add_field(body, boundary, 'webhook_url', webhook_url) if webhook_url
    body << "--#{boundary}--\r\n"

    request = Net::HTTP::Post.new(uri('/api/v2/scrub/file'))
    request['Authorization'] = "Bearer #{@token}"
    request['Accept'] = 'application/json'
    request['Content-Type'] = "multipart/form-data; boundary=#{boundary}"
    request.body = body

    parse_response(request)
  end

  private

  def post_json(path, payload)
    request = Net::HTTP::Post.new(uri(path))
    request['Authorization'] = "Bearer #{@token}"
    request['Accept'] = 'application/json'
    request['Content-Type'] = 'application/json'
    request.body = payload.to_json

    parse_response(request)
  end

  def parse_response(request)
    response = Net::HTTP.start(request.uri.hostname, request.uri.port, use_ssl: true) do |http|
      http.request(request)
    end

    unless response.is_a?(Net::HTTPSuccess)
      raise "DNC API failed #{response.code}: #{response.body}"
    end

    JSON.parse(response.body)
  end

  def uri(path)
    URI("#{@base_url}#{path}")
  end

  def add_field(body, boundary, name, value)
    body << "--#{boundary}\r\n"
    body << "Content-Disposition: form-data; name=\"#{name}\"\r\n\r\n"
    body << "#{value}\r\n"
  end

  def add_file(body, boundary, name, path, content_type)
    body << "--#{boundary}\r\n"
    body << "Content-Disposition: form-data; name=\"#{name}\"; filename=\"#{File.basename(path)}\"\r\n"
    body << "Content-Type: #{content_type}\r\n\r\n"
    body << File.binread(path)
    body << "\r\n"
  end
end
Java 8 DNC API client
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.UUID;

public final class DncProjectClient {
    private final String token;
    private final String baseUrl;

    public DncProjectClient(String token) {
        this.token = token;
        this.baseUrl = "https://thedncproject.org";
    }

    public String scrubNumbers(List<String> numbers, List<String> scrubTypes) throws Exception {
        String body = "{\"numbers\":" + jsonArray(numbers) + ",\"scrub_type\":" + jsonArray(scrubTypes) + "}";

        return post("/api/v2/scrubs", "application/json", body.getBytes(StandardCharsets.UTF_8));
    }

    public String uploadFile(Path filePath, List<String> scrubTypes, List<Integer> columns, String webhookUrl) throws Exception {
        String boundary = "----DNC" + UUID.randomUUID();
        ByteArrayOutputStream multipart = new ByteArrayOutputStream();

        addFile(multipart, boundary, "file", filePath);

        for (String scrubType : scrubTypes) {
            addField(multipart, boundary, "scrub_type[]", scrubType);
        }

        for (Integer column : columns) {
            addField(multipart, boundary, "scrub_column[]", column.toString());
        }

        if (webhookUrl != null && !webhookUrl.trim().isEmpty()) {
            addField(multipart, boundary, "webhook_url", webhookUrl);
        }

        writeText(multipart, "--" + boundary + "--\r\n");

        return post("/api/v2/scrub/file", "multipart/form-data; boundary=" + boundary, multipart.toByteArray());
    }

    private String post(String path, String contentType, byte[] body) throws Exception {
        HttpURLConnection connection = (HttpURLConnection) new URL(baseUrl + path).openConnection();
        connection.setRequestMethod("POST");
        connection.setDoOutput(true);
        connection.setRequestProperty("Authorization", "Bearer " + token);
        connection.setRequestProperty("Accept", "application/json");
        connection.setRequestProperty("Content-Type", contentType);

        try (OutputStream output = connection.getOutputStream()) {
            output.write(body);
        }

        int status = connection.getResponseCode();
        String response = read(status >= 300 ? connection.getErrorStream() : connection.getInputStream());

        if (status >= 300) {
            throw new RuntimeException("DNC API failed " + status + ": " + response);
        }

        return response;
    }

    private static String jsonArray(List<String> values) {
        StringBuilder json = new StringBuilder("[");

        for (int index = 0; index < values.size(); index++) {
            if (index > 0) {
                json.append(",");
            }

            json.append(jsonString(values.get(index)));
        }

        return json.append("]").toString();
    }

    private static String jsonString(String value) {
        return "\"" + value.replace("\\", "\\\\").replace("\"", "\\\"") + "\"";
    }

    private static String read(InputStream stream) throws Exception {
        if (stream == null) {
            return "";
        }

        ByteArrayOutputStream body = new ByteArrayOutputStream();
        byte[] buffer = new byte[8192];
        int count;

        while ((count = stream.read(buffer)) != -1) {
            body.write(buffer, 0, count);
        }

        return new String(body.toByteArray(), StandardCharsets.UTF_8);
    }

    private static void writeText(ByteArrayOutputStream body, String value) throws Exception {
        body.write(value.getBytes(StandardCharsets.UTF_8));
    }

    private static void addField(ByteArrayOutputStream body, String boundary, String name, String value) throws Exception {
        writeText(body, "--" + boundary + "\r\n");
        writeText(body, "Content-Disposition: form-data; name=\"" + name + "\"\r\n\r\n");
        writeText(body, value + "\r\n");
    }

    private static void addFile(ByteArrayOutputStream body, String boundary, String name, Path path) throws Exception {
        writeText(body, "--" + boundary + "\r\n");
        writeText(body, "Content-Disposition: form-data; name=\"" + name + "\"; filename=\"" + path.getFileName() + "\"\r\n");
        writeText(body, "Content-Type: text/csv\r\n\r\n");
        body.write(Files.readAllBytes(path));
        writeText(body, "\r\n");
    }
}
Java 11+ DNC API client
import java.io.ByteArrayOutputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

public final class DncProjectClient {
    private final String token;
    private final String baseUrl;
    private final HttpClient http;

    public DncProjectClient(String token) {
        this.token = token;
        this.baseUrl = "https://thedncproject.org";
        this.http = HttpClient.newHttpClient();
    }

    public String scrubNumbers(List<String> numbers, List<String> scrubTypes) throws Exception {
        String body = "{\"numbers\":" + jsonArray(numbers) + ",\"scrub_type\":" + jsonArray(scrubTypes) + "}";

        return post(
            "/api/v2/scrubs",
            "application/json",
            HttpRequest.BodyPublishers.ofString(body)
        );
    }

    public String uploadFile(Path filePath, List<String> scrubTypes, List<Integer> columns, String webhookUrl) throws Exception {
        String boundary = "----DNC" + UUID.randomUUID();
        ByteArrayOutputStream multipart = new ByteArrayOutputStream();

        addFile(multipart, boundary, "file", filePath);

        for (String scrubType : scrubTypes) {
            addField(multipart, boundary, "scrub_type[]", scrubType);
        }

        for (Integer column : columns) {
            addField(multipart, boundary, "scrub_column[]", column.toString());
        }

        if (webhookUrl != null && !webhookUrl.isBlank()) {
            addField(multipart, boundary, "webhook_url", webhookUrl);
        }

        writeText(multipart, "--" + boundary + "--\r\n");

        return post(
            "/api/v2/scrub/file",
            "multipart/form-data; boundary=" + boundary,
            HttpRequest.BodyPublishers.ofByteArray(multipart.toByteArray())
        );
    }

    private String post(String path, String contentType, HttpRequest.BodyPublisher body) throws Exception {
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(baseUrl + path))
            .header("Authorization", "Bearer " + token)
            .header("Accept", "application/json")
            .header("Content-Type", contentType)
            .POST(body)
            .build();

        HttpResponse<String> response = http.send(request, HttpResponse.BodyHandlers.ofString());

        if (response.statusCode() >= 300) {
            throw new RuntimeException("DNC API failed " + response.statusCode() + ": " + response.body());
        }

        return response.body();
    }

    private static String jsonArray(List<String> values) {
        return values.stream().map(DncProjectClient::jsonString).collect(Collectors.joining(",", "[", "]"));
    }

    private static String jsonString(String value) {
        return "\"" + value.replace("\\", "\\\\").replace("\"", "\\\"") + "\"";
    }

    private static void writeText(ByteArrayOutputStream body, String value) throws Exception {
        body.write(value.getBytes(StandardCharsets.UTF_8));
    }

    private static void addField(ByteArrayOutputStream body, String boundary, String name, String value) throws Exception {
        writeText(body, "--" + boundary + "\r\n");
        writeText(body, "Content-Disposition: form-data; name=\"" + name + "\"\r\n\r\n");
        writeText(body, value + "\r\n");
    }

    private static void addFile(ByteArrayOutputStream body, String boundary, String name, Path path) throws Exception {
        writeText(body, "--" + boundary + "\r\n");
        writeText(body, "Content-Disposition: form-data; name=\"" + name + "\"; filename=\"" + path.getFileName() + "\"\r\n");
        writeText(body, "Content-Type: text/csv\r\n\r\n");
        body.write(Files.readAllBytes(path));
        writeText(body, "\r\n");
    }
}
Java 17+ DNC API client
import java.io.ByteArrayOutputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

public final class DncProjectClient {
    private final String token;
    private final String baseUrl;
    private final HttpClient http;

    public DncProjectClient(String token) {
        this.token = token;
        this.baseUrl = "https://thedncproject.org";
        this.http = HttpClient.newHttpClient();
    }

    public String scrubNumbers(List<String> numbers, List<String> scrubTypes) throws Exception {
        String body = "{\"numbers\":" + jsonArray(numbers) + ",\"scrub_type\":" + jsonArray(scrubTypes) + "}";

        return post(
            "/api/v2/scrubs",
            "application/json",
            HttpRequest.BodyPublishers.ofString(body)
        );
    }

    public String uploadFile(Path filePath, List<String> scrubTypes, List<Integer> columns, String webhookUrl) throws Exception {
        String boundary = "----DNC" + UUID.randomUUID();
        ByteArrayOutputStream multipart = new ByteArrayOutputStream();

        addFile(multipart, boundary, "file", filePath);

        for (String scrubType : scrubTypes) {
            addField(multipart, boundary, "scrub_type[]", scrubType);
        }

        for (Integer column : columns) {
            addField(multipart, boundary, "scrub_column[]", column.toString());
        }

        if (webhookUrl != null && !webhookUrl.isBlank()) {
            addField(multipart, boundary, "webhook_url", webhookUrl);
        }

        writeText(multipart, "--" + boundary + "--\r\n");

        return post(
            "/api/v2/scrub/file",
            "multipart/form-data; boundary=" + boundary,
            HttpRequest.BodyPublishers.ofByteArray(multipart.toByteArray())
        );
    }

    private String post(String path, String contentType, HttpRequest.BodyPublisher body) throws Exception {
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(baseUrl + path))
            .header("Authorization", "Bearer " + token)
            .header("Accept", "application/json")
            .header("Content-Type", contentType)
            .POST(body)
            .build();

        HttpResponse<String> response = http.send(request, HttpResponse.BodyHandlers.ofString());

        if (response.statusCode() >= 300) {
            throw new RuntimeException("DNC API failed " + response.statusCode() + ": " + response.body());
        }

        return response.body();
    }

    private static String jsonArray(List<String> values) {
        return values.stream().map(DncProjectClient::jsonString).collect(Collectors.joining(",", "[", "]"));
    }

    private static String jsonString(String value) {
        return "\"" + value.replace("\\", "\\\\").replace("\"", "\\\"") + "\"";
    }

    private static void writeText(ByteArrayOutputStream body, String value) throws Exception {
        body.write(value.getBytes(StandardCharsets.UTF_8));
    }

    private static void addField(ByteArrayOutputStream body, String boundary, String name, String value) throws Exception {
        writeText(body, "--" + boundary + "\r\n");
        writeText(body, "Content-Disposition: form-data; name=\"" + name + "\"\r\n\r\n");
        writeText(body, value + "\r\n");
    }

    private static void addFile(ByteArrayOutputStream body, String boundary, String name, Path path) throws Exception {
        writeText(body, "--" + boundary + "\r\n");
        writeText(body, "Content-Disposition: form-data; name=\"" + name + "\"; filename=\"" + path.getFileName() + "\"\r\n");
        writeText(body, "Content-Type: text/csv\r\n\r\n");
        body.write(Files.readAllBytes(path));
        writeText(body, "\r\n");
    }
}
.NET Framework 4.7.2 DNC API client
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;

public sealed class DncProjectClient
{
    private readonly HttpClient http;
    private readonly string baseUrl;

    public DncProjectClient(string token, HttpClient httpClient = null, string baseUrl = "https://thedncproject.org")
    {
        this.http = httpClient ?? new HttpClient();
        this.baseUrl = baseUrl.TrimEnd('/');
        this.http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
        this.http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    }

    public Task<string> ScrubNumbersAsync(IEnumerable<string> numbers, IEnumerable<string> scrubTypes)
    {
        string payload = "{\"numbers\":" + JsonArray(numbers) + ",\"scrub_type\":" + JsonArray(scrubTypes) + "}";

        return PostAsync(
            "/api/v2/scrubs",
            new StringContent(payload, Encoding.UTF8, "application/json")
        );
    }

    public async Task<string> UploadFileAsync(string filePath, IEnumerable<string> scrubTypes, IEnumerable<int> columns, string webhookUrl = null)
    {
        using (var form = new MultipartFormDataContent())
        using (var file = File.OpenRead(filePath)) {
            form.Add(new StreamContent(file), "file", Path.GetFileName(filePath));

            foreach (var scrubType in scrubTypes) {
                form.Add(new StringContent(scrubType), "scrub_type[]");
            }

            foreach (var column in columns) {
                form.Add(new StringContent(column.ToString()), "scrub_column[]");
            }

            if (!string.IsNullOrWhiteSpace(webhookUrl)) {
                form.Add(new StringContent(webhookUrl), "webhook_url");
            }

            return await PostAsync("/api/v2/scrub/file", form);
        }
    }

    private async Task<string> PostAsync(string path, HttpContent content)
    {
        using (var request = new HttpRequestMessage(HttpMethod.Post, baseUrl + path)) {
            request.Content = content;

            using (var response = await http.SendAsync(request)) {
                string body = await response.Content.ReadAsStringAsync();

                if (!response.IsSuccessStatusCode) {
                    throw new Exception("DNC API failed " + (int) response.StatusCode + ": " + body);
                }

                return body;
            }
        }
    }

    private static string JsonArray(IEnumerable<string> values)
    {
        return "[" + string.Join(",", values.Select(JsonString)) + "]";
    }

    private static string JsonString(string value)
    {
        return "\"" + value.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\"";
    }
}
.NET 6+ DNC API client
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

public sealed class DncProjectClient
{
    private readonly HttpClient http;
    private readonly string baseUrl;

    public DncProjectClient(string token, HttpClient? httpClient = null, string baseUrl = "https://thedncproject.org")
    {
        this.http = httpClient ?? new HttpClient();
        this.baseUrl = baseUrl.TrimEnd('/');
        this.http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
        this.http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    }

    public async Task<JsonDocument> ScrubNumbersAsync(IEnumerable<string> numbers, IEnumerable<string> scrubTypes)
    {
        var payload = JsonSerializer.Serialize(new {
            numbers,
            scrub_type = scrubTypes,
        });

        var body = await PostAsync(
            "/api/v2/scrubs",
            new StringContent(payload, Encoding.UTF8, "application/json")
        );

        return JsonDocument.Parse(body);
    }

    public async Task<string> UploadFileAsync(string filePath, IEnumerable<string> scrubTypes, IEnumerable<int> columns, string? webhookUrl = null)
    {
        using var form = new MultipartFormDataContent();
        await using var file = File.OpenRead(filePath);

        form.Add(new StreamContent(file), "file", Path.GetFileName(filePath));

        foreach (var scrubType in scrubTypes) {
            form.Add(new StringContent(scrubType), "scrub_type[]");
        }

        foreach (var column in columns) {
            form.Add(new StringContent(column.ToString()), "scrub_column[]");
        }

        if (!string.IsNullOrWhiteSpace(webhookUrl)) {
            form.Add(new StringContent(webhookUrl), "webhook_url");
        }

        return await PostAsync("/api/v2/scrub/file", form);
    }

    private async Task<string> PostAsync(string path, HttpContent content)
    {
        using var request = new HttpRequestMessage(HttpMethod.Post, $"{baseUrl}{path}") {
            Content = content,
        };

        using var response = await http.SendAsync(request);
        var body = await response.Content.ReadAsStringAsync();

        if (!response.IsSuccessStatusCode) {
            throw new Exception($"DNC API failed {(int) response.StatusCode}: {body}");
        }

        return body;
    }
}
.NET 8+ DNC API client
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

public sealed class DncProjectClient
{
    private readonly HttpClient http;
    private readonly string baseUrl;

    public DncProjectClient(string token, HttpClient? httpClient = null, string baseUrl = "https://thedncproject.org")
    {
        this.http = httpClient ?? new HttpClient();
        this.baseUrl = baseUrl.TrimEnd('/');
        this.http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
        this.http.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    }

    public async Task<JsonDocument> ScrubNumbersAsync(IEnumerable<string> numbers, IEnumerable<string> scrubTypes)
    {
        var payload = JsonSerializer.Serialize(new {
            numbers,
            scrub_type = scrubTypes,
        });

        var body = await PostAsync(
            "/api/v2/scrubs",
            new StringContent(payload, Encoding.UTF8, "application/json")
        );

        return JsonDocument.Parse(body);
    }

    public async Task<string> UploadFileAsync(string filePath, IEnumerable<string> scrubTypes, IEnumerable<int> columns, string? webhookUrl = null)
    {
        using var form = new MultipartFormDataContent();
        await using var file = File.OpenRead(filePath);

        form.Add(new StreamContent(file), "file", Path.GetFileName(filePath));

        foreach (var scrubType in scrubTypes) {
            form.Add(new StringContent(scrubType), "scrub_type[]");
        }

        foreach (var column in columns) {
            form.Add(new StringContent(column.ToString()), "scrub_column[]");
        }

        if (!string.IsNullOrWhiteSpace(webhookUrl)) {
            form.Add(new StringContent(webhookUrl), "webhook_url");
        }

        return await PostAsync("/api/v2/scrub/file", form);
    }

    private async Task<string> PostAsync(string path, HttpContent content)
    {
        using var request = new HttpRequestMessage(HttpMethod.Post, $"{baseUrl}{path}") {
            Content = content,
        };

        using var response = await http.SendAsync(request);
        var body = await response.Content.ReadAsStringAsync();

        if (!response.IsSuccessStatusCode) {
            throw new Exception($"DNC API failed {(int) response.StatusCode}: {body}");
        }

        return body;
    }
}
C++11 DNC API client
#include <curl/curl.h>
#include <cstddef>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>

class DncProjectClient {
public:
    explicit DncProjectClient(std::string token, std::string baseUrl = "https://thedncproject.org")
        : token_(std::move(token)), baseUrl_(trimTrailingSlash(std::move(baseUrl))) {}

    std::string scrubNumbers(const std::vector<std::string>& numbers, const std::vector<std::string>& scrubTypes) const
    {
        std::string payload = "{\"numbers\":" + jsonArray(numbers)
            + ",\"scrub_type\":" + jsonArray(scrubTypes) + "}";

        return postJson("/api/v2/scrubs", payload);
    }

    std::string uploadFile(
        const std::string& filePath,
        const std::vector<std::string>& scrubTypes,
        const std::vector<int>& columns,
        const std::string& webhookUrl = ""
    ) const {
        CURL* curl = createRequest("/api/v2/scrub/file");
        curl_mime* mime = curl_mime_init(curl);
        curl_slist* headers = authHeaders();

        addFile(mime, "file", filePath);

        for (const std::string& scrubType : scrubTypes) {
            addField(mime, "scrub_type[]", scrubType);
        }

        for (int column : columns) {
            addField(mime, "scrub_column[]", std::to_string(column));
        }

        if (!webhookUrl.empty()) {
            addField(mime, "webhook_url", webhookUrl);
        }

        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
        curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime);

        std::string response = perform(curl, "DNC file API");
        curl_mime_free(mime);
        curl_slist_free_all(headers);
        curl_easy_cleanup(curl);

        return response;
    }

private:
    static std::string trimTrailingSlash(std::string value)
    {
        if (!value.empty() && value.back() == '/') {
            value.pop_back();
        }

        return value;
    }

    CURL* createRequest(const std::string& path) const
    {
        CURL* curl = curl_easy_init();

        if (!curl) {
            throw std::runtime_error("Unable to initialize libcurl");
        }

        curl_easy_setopt(curl, CURLOPT_URL, (baseUrl_ + path).c_str());
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeResponse);

        return curl;
    }

    std::string postJson(const std::string& path, const std::string& payload) const
    {
        CURL* curl = createRequest(path);
        curl_slist* headers = authHeaders();
        headers = curl_slist_append(headers, "Content-Type: application/json");

        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload.c_str());

        std::string response = perform(curl, "DNC API");
        curl_slist_free_all(headers);
        curl_easy_cleanup(curl);

        return response;
    }

    curl_slist* authHeaders() const
    {
        std::string auth = "Authorization: Bearer " + token_;
        curl_slist* headers = nullptr;
        headers = curl_slist_append(headers, auth.c_str());
        headers = curl_slist_append(headers, "Accept: application/json");

        return headers;
    }

    static void addField(curl_mime* mime, const std::string& name, const std::string& value)
    {
        curl_mimepart* part = curl_mime_addpart(mime);
        curl_mime_name(part, name.c_str());
        curl_mime_data(part, value.c_str(), CURL_ZERO_TERMINATED);
    }

    static void addFile(curl_mime* mime, const std::string& name, const std::string& filePath)
    {
        curl_mimepart* part = curl_mime_addpart(mime);
        curl_mime_name(part, name.c_str());
        curl_mime_filedata(part, filePath.c_str());
    }

    static std::string jsonArray(const std::vector<std::string>& values)
    {
        std::string json = "[";

        for (std::size_t index = 0; index < values.size(); ++index) {
            if (index > 0) {
                json += ",";
            }

            json += "\"" + jsonEscape(values[index]) + "\"";
        }

        return json + "]";
    }

    static std::string jsonEscape(const std::string& value)
    {
        std::string escaped;

        for (char character : value) {
            if (character == '\\' || character == '"') {
                escaped += '\\';
            }

            escaped += character;
        }

        return escaped;
    }

    static std::string perform(CURL* curl, const std::string& label)
    {
        std::string response;
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

        CURLcode result = curl_easy_perform(curl);
        long status = 0;
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);

        if (result != CURLE_OK) {
            throw std::runtime_error(label + " request failed: " + curl_easy_strerror(result));
        }

        if (status >= 300) {
            throw std::runtime_error(label + " failed " + std::to_string(status) + ": " + response);
        }

        return response;
    }

    static size_t writeResponse(char* data, size_t size, size_t count, void* output)
    {
        std::string* response = static_cast<std::string*>(output);
        response->append(data, size * count);

        return size * count;
    }

    std::string token_;
    std::string baseUrl_;
};
C++17 DNC API client
#include <curl/curl.h>
#include <cstddef>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>

class DncProjectClient {
public:
    explicit DncProjectClient(std::string token, std::string baseUrl = "https://thedncproject.org")
        : token_(std::move(token)), baseUrl_(trimTrailingSlash(std::move(baseUrl))) {}

    std::string scrubNumbers(const std::vector<std::string>& numbers, const std::vector<std::string>& scrubTypes) const
    {
        std::string payload = "{\"numbers\":" + jsonArray(numbers)
            + ",\"scrub_type\":" + jsonArray(scrubTypes) + "}";

        return postJson("/api/v2/scrubs", payload);
    }

    std::string uploadFile(
        const std::string& filePath,
        const std::vector<std::string>& scrubTypes,
        const std::vector<int>& columns,
        const std::string& webhookUrl = ""
    ) const {
        CURL* curl = createRequest("/api/v2/scrub/file");
        curl_mime* mime = curl_mime_init(curl);
        curl_slist* headers = authHeaders();

        addFile(mime, "file", filePath);

        for (const std::string& scrubType : scrubTypes) {
            addField(mime, "scrub_type[]", scrubType);
        }

        for (int column : columns) {
            addField(mime, "scrub_column[]", std::to_string(column));
        }

        if (!webhookUrl.empty()) {
            addField(mime, "webhook_url", webhookUrl);
        }

        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
        curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime);

        std::string response = perform(curl, "DNC file API");
        curl_mime_free(mime);
        curl_slist_free_all(headers);
        curl_easy_cleanup(curl);

        return response;
    }

private:
    static std::string trimTrailingSlash(std::string value)
    {
        if (!value.empty() && value.back() == '/') {
            value.pop_back();
        }

        return value;
    }

    CURL* createRequest(const std::string& path) const
    {
        CURL* curl = curl_easy_init();

        if (!curl) {
            throw std::runtime_error("Unable to initialize libcurl");
        }

        curl_easy_setopt(curl, CURLOPT_URL, (baseUrl_ + path).c_str());
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeResponse);

        return curl;
    }

    std::string postJson(const std::string& path, const std::string& payload) const
    {
        CURL* curl = createRequest(path);
        curl_slist* headers = authHeaders();
        headers = curl_slist_append(headers, "Content-Type: application/json");

        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload.c_str());

        std::string response = perform(curl, "DNC API");
        curl_slist_free_all(headers);
        curl_easy_cleanup(curl);

        return response;
    }

    curl_slist* authHeaders() const
    {
        std::string auth = "Authorization: Bearer " + token_;
        curl_slist* headers = nullptr;
        headers = curl_slist_append(headers, auth.c_str());
        headers = curl_slist_append(headers, "Accept: application/json");

        return headers;
    }

    static void addField(curl_mime* mime, const std::string& name, const std::string& value)
    {
        curl_mimepart* part = curl_mime_addpart(mime);
        curl_mime_name(part, name.c_str());
        curl_mime_data(part, value.c_str(), CURL_ZERO_TERMINATED);
    }

    static void addFile(curl_mime* mime, const std::string& name, const std::string& filePath)
    {
        curl_mimepart* part = curl_mime_addpart(mime);
        curl_mime_name(part, name.c_str());
        curl_mime_filedata(part, filePath.c_str());
    }

    static std::string jsonArray(const std::vector<std::string>& values)
    {
        std::string json = "[";

        for (std::size_t index = 0; index < values.size(); ++index) {
            if (index > 0) {
                json += ",";
            }

            json += "\"" + jsonEscape(values[index]) + "\"";
        }

        return json + "]";
    }

    static std::string jsonEscape(const std::string& value)
    {
        std::string escaped;

        for (char character : value) {
            if (character == '\\' || character == '"') {
                escaped += '\\';
            }

            escaped += character;
        }

        return escaped;
    }

    static std::string perform(CURL* curl, const std::string& label)
    {
        std::string response;
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

        CURLcode result = curl_easy_perform(curl);
        long status = 0;
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);

        if (result != CURLE_OK) {
            throw std::runtime_error(label + " request failed: " + curl_easy_strerror(result));
        }

        if (status >= 300) {
            throw std::runtime_error(label + " failed " + std::to_string(status) + ": " + response);
        }

        return response;
    }

    static size_t writeResponse(char* data, size_t size, size_t count, void* output)
    {
        std::string* response = static_cast<std::string*>(output);
        response->append(data, size * count);

        return size * count;
    }

    std::string token_;
    std::string baseUrl_;
};
C++20 DNC API client
#include <curl/curl.h>
#include <cstddef>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>

class DncProjectClient {
public:
    explicit DncProjectClient(std::string token, std::string baseUrl = "https://thedncproject.org")
        : token_(std::move(token)), baseUrl_(trimTrailingSlash(std::move(baseUrl))) {}

    std::string scrubNumbers(const std::vector<std::string>& numbers, const std::vector<std::string>& scrubTypes) const
    {
        std::string payload = "{\"numbers\":" + jsonArray(numbers)
            + ",\"scrub_type\":" + jsonArray(scrubTypes) + "}";

        return postJson("/api/v2/scrubs", payload);
    }

    std::string uploadFile(
        const std::string& filePath,
        const std::vector<std::string>& scrubTypes,
        const std::vector<int>& columns,
        const std::string& webhookUrl = ""
    ) const {
        CURL* curl = createRequest("/api/v2/scrub/file");
        curl_mime* mime = curl_mime_init(curl);
        curl_slist* headers = authHeaders();

        addFile(mime, "file", filePath);

        for (const std::string& scrubType : scrubTypes) {
            addField(mime, "scrub_type[]", scrubType);
        }

        for (int column : columns) {
            addField(mime, "scrub_column[]", std::to_string(column));
        }

        if (!webhookUrl.empty()) {
            addField(mime, "webhook_url", webhookUrl);
        }

        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
        curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime);

        std::string response = perform(curl, "DNC file API");
        curl_mime_free(mime);
        curl_slist_free_all(headers);
        curl_easy_cleanup(curl);

        return response;
    }

private:
    static std::string trimTrailingSlash(std::string value)
    {
        if (!value.empty() && value.back() == '/') {
            value.pop_back();
        }

        return value;
    }

    CURL* createRequest(const std::string& path) const
    {
        CURL* curl = curl_easy_init();

        if (!curl) {
            throw std::runtime_error("Unable to initialize libcurl");
        }

        curl_easy_setopt(curl, CURLOPT_URL, (baseUrl_ + path).c_str());
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeResponse);

        return curl;
    }

    std::string postJson(const std::string& path, const std::string& payload) const
    {
        CURL* curl = createRequest(path);
        curl_slist* headers = authHeaders();
        headers = curl_slist_append(headers, "Content-Type: application/json");

        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload.c_str());

        std::string response = perform(curl, "DNC API");
        curl_slist_free_all(headers);
        curl_easy_cleanup(curl);

        return response;
    }

    curl_slist* authHeaders() const
    {
        std::string auth = "Authorization: Bearer " + token_;
        curl_slist* headers = nullptr;
        headers = curl_slist_append(headers, auth.c_str());
        headers = curl_slist_append(headers, "Accept: application/json");

        return headers;
    }

    static void addField(curl_mime* mime, const std::string& name, const std::string& value)
    {
        curl_mimepart* part = curl_mime_addpart(mime);
        curl_mime_name(part, name.c_str());
        curl_mime_data(part, value.c_str(), CURL_ZERO_TERMINATED);
    }

    static void addFile(curl_mime* mime, const std::string& name, const std::string& filePath)
    {
        curl_mimepart* part = curl_mime_addpart(mime);
        curl_mime_name(part, name.c_str());
        curl_mime_filedata(part, filePath.c_str());
    }

    static std::string jsonArray(const std::vector<std::string>& values)
    {
        std::string json = "[";

        for (std::size_t index = 0; index < values.size(); ++index) {
            if (index > 0) {
                json += ",";
            }

            json += "\"" + jsonEscape(values[index]) + "\"";
        }

        return json + "]";
    }

    static std::string jsonEscape(const std::string& value)
    {
        std::string escaped;

        for (char character : value) {
            if (character == '\\' || character == '"') {
                escaped += '\\';
            }

            escaped += character;
        }

        return escaped;
    }

    static std::string perform(CURL* curl, const std::string& label)
    {
        std::string response;
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);

        CURLcode result = curl_easy_perform(curl);
        long status = 0;
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);

        if (result != CURLE_OK) {
            throw std::runtime_error(label + " request failed: " + curl_easy_strerror(result));
        }

        if (status >= 300) {
            throw std::runtime_error(label + " failed " + std::to_string(status) + ": " + response);
        }

        return response;
    }

    static size_t writeResponse(char* data, size_t size, size_t count, void* output)
    {
        std::string* response = static_cast<std::string*>(output);
        response->append(data, size * count);

        return size * count;
    }

    std::string token_;
    std::string baseUrl_;
};
POST /api/v2/scrubs PHP 5.4 request
$client = new DncProjectClient('YOUR_TOKEN_HERE');

$result = $client->scrubNumbers(
    array('3105551212', '3105551213', '3105551214'),
    array('dnc', 'state', 'litigator')
);
POST /api/v2/scrubs PHP 7.0 request
$client = new DncProjectClient('YOUR_TOKEN_HERE');

$result = $client->scrubNumbers(
    ['3105551212', '3105551213', '3105551214'],
    ['dnc', 'state', 'litigator']
);
POST /api/v2/scrubs PHP 7.4 request
$client = new DncProjectClient('YOUR_TOKEN_HERE');

$result = $client->scrubNumbers(
    ['3105551212', '3105551213', '3105551214'],
    ['dnc', 'state', 'litigator']
);
POST /api/v2/scrubs PHP 8.0 request
$client = new DncProjectClient('YOUR_TOKEN_HERE');

$result = $client->scrubNumbers(
    ['3105551212', '3105551213', '3105551214'],
    ['dnc', 'state', 'litigator']
);
POST /api/v2/scrubs PHP 8.1 request
$client = new DncProjectClient('YOUR_TOKEN_HERE');

$result = $client->scrubNumbers(
    ['3105551212', '3105551213', '3105551214'],
    ['dnc', 'state', 'litigator']
);
POST /api/v2/scrubs PHP 8.2 request
$client = new DncProjectClient('YOUR_TOKEN_HERE');

$result = $client->scrubNumbers(
    ['3105551212', '3105551213', '3105551214'],
    ['dnc', 'state', 'litigator']
);
POST /api/v2/scrubs PHP 8.3 request
$client = new DncProjectClient('YOUR_TOKEN_HERE');

$result = $client->scrubNumbers(
    ['3105551212', '3105551213', '3105551214'],
    ['dnc', 'state', 'litigator']
);
POST /api/v2/scrubs PHP 8.4 request
$client = new DncProjectClient('YOUR_TOKEN_HERE');

$result = $client->scrubNumbers(
    ['3105551212', '3105551213', '3105551214'],
    ['dnc', 'state', 'litigator']
);
POST /api/v2/scrubs PHP 8.5 request
$client = new DncProjectClient('YOUR_TOKEN_HERE');

$result = $client->scrubNumbers(
    ['3105551212', '3105551213', '3105551214'],
    ['dnc', 'state', 'litigator']
);
POST /api/v2/scrubs ES5 request
var client = new DncProjectClient('YOUR_TOKEN_HERE');

client.scrubNumbers(
    ['3105551212', '3105551213', '3105551214'],
    ['dnc', 'state', 'litigator'],
    function (error, result) {
        if (error) {
            throw error;
        }

        console.log(result);
    }
);
POST /api/v2/scrubs ES2017+ request
const client = new DncProjectClient('YOUR_TOKEN_HERE');

const result = await client.scrubNumbers(
    ['3105551212', '3105551213', '3105551214'],
    ['dnc', 'state', 'litigator']
);
POST /api/v2/scrubs Python 2.7 request
client = DncProjectClient('YOUR_TOKEN_HERE')

result = client.scrub_numbers(
    ['3105551212', '3105551213', '3105551214'],
    ['dnc', 'state', 'litigator'],
)
POST /api/v2/scrubs Python 3.6 request
client = DncProjectClient('YOUR_TOKEN_HERE')

result = client.scrub_numbers(
    ['3105551212', '3105551213', '3105551214'],
    ['dnc', 'state', 'litigator'],
)
POST /api/v2/scrubs Python 3.8 request
client = DncProjectClient('YOUR_TOKEN_HERE')

result = client.scrub_numbers(
    ['3105551212', '3105551213', '3105551214'],
    ['dnc', 'state', 'litigator'],
)
POST /api/v2/scrubs Python 3.10+ request
client = DncProjectClient('YOUR_TOKEN_HERE')

result = client.scrub_numbers(
    ['3105551212', '3105551213', '3105551214'],
    ['dnc', 'state', 'litigator'],
)
POST /api/v2/scrubs Go 1.13 request
client := dncproject.Client{
    Token: "YOUR_TOKEN_HERE",
    BaseURL: "https://thedncproject.org",
}

result, err := client.ScrubNumbers(
    context.Background(),
    []string{"3105551212", "3105551213", "3105551214"},
    []string{"dnc", "state", "litigator"},
)
if err != nil {
    panic(err)
}

fmt.Println(string(result))
POST /api/v2/scrubs Go 1.16+ request
client := dncproject.Client{
    Token: "YOUR_TOKEN_HERE",
    BaseURL: "https://thedncproject.org",
}

result, err := client.ScrubNumbers(
    context.Background(),
    []string{"3105551212", "3105551213", "3105551214"},
    []string{"dnc", "state", "litigator"},
)
if err != nil {
    panic(err)
}

fmt.Println(string(result))
POST /api/v2/scrubs Go stable request
client := dncproject.Client{
    Token: "YOUR_TOKEN_HERE",
    BaseURL: "https://thedncproject.org",
}

result, err := client.ScrubNumbers(
    context.Background(),
    []string{"3105551212", "3105551213", "3105551214"},
    []string{"dnc", "state", "litigator"},
)
if err != nil {
    panic(err)
}

fmt.Println(string(result))
POST /api/v2/scrubs Rust 1.56+ request
let client = DncProjectClient::new("YOUR_TOKEN_HERE");

let result = client.scrub_numbers(
    &["3105551212", "3105551213", "3105551214"],
    &["dnc", "state", "litigator"],
)?;

println!("{}", serde_json::to_string_pretty(&result)?);
POST /api/v2/scrubs Rust 1.70+ request
let client = DncProjectClient::new("YOUR_TOKEN_HERE");

let result = client.scrub_numbers(
    &["3105551212", "3105551213", "3105551214"],
    &["dnc", "state", "litigator"],
)?;

println!("{}", serde_json::to_string_pretty(&result)?);
POST /api/v2/scrubs Rust stable request
let client = DncProjectClient::new("YOUR_TOKEN_HERE");

let result = client.scrub_numbers(
    &["3105551212", "3105551213", "3105551214"],
    &["dnc", "state", "litigator"],
)?;

println!("{}", serde_json::to_string_pretty(&result)?);
POST /api/v2/scrubs Ruby 2.7 request
client = DncProjectClient.new('YOUR_TOKEN_HERE')

result = client.scrub_numbers(
  ['3105551212', '3105551213', '3105551214'],
  ['dnc', 'state', 'litigator']
)
POST /api/v2/scrubs Ruby 3.0 request
client = DncProjectClient.new('YOUR_TOKEN_HERE')

result = client.scrub_numbers(
  ['3105551212', '3105551213', '3105551214'],
  ['dnc', 'state', 'litigator']
)
POST /api/v2/scrubs Ruby 3.1+ request
client = DncProjectClient.new('YOUR_TOKEN_HERE')

result = client.scrub_numbers(
  ['3105551212', '3105551213', '3105551214'],
  ['dnc', 'state', 'litigator']
)
POST /api/v2/scrubs Java 8 request
DncProjectClient client = new DncProjectClient("YOUR_TOKEN_HERE");

String result = client.scrubNumbers(
    Arrays.asList("3105551212", "3105551213", "3105551214"),
    Arrays.asList("dnc", "state", "litigator")
);

System.out.println(result);
POST /api/v2/scrubs Java 11+ request
DncProjectClient client = new DncProjectClient("YOUR_TOKEN_HERE");

String result = client.scrubNumbers(
    List.of("3105551212", "3105551213", "3105551214"),
    List.of("dnc", "state", "litigator")
);

System.out.println(result);
POST /api/v2/scrubs Java 17+ request
DncProjectClient client = new DncProjectClient("YOUR_TOKEN_HERE");

String result = client.scrubNumbers(
    List.of("3105551212", "3105551213", "3105551214"),
    List.of("dnc", "state", "litigator")
);

System.out.println(result);
POST /api/v2/scrubs .NET Framework 4.7.2 request
var client = new DncProjectClient("YOUR_TOKEN_HERE");

var result = await client.ScrubNumbersAsync(
    new[] { "3105551212", "3105551213", "3105551214" },
    new[] { "dnc", "state", "litigator" }
);

Console.WriteLine(result);
POST /api/v2/scrubs .NET 6+ request
var client = new DncProjectClient("YOUR_TOKEN_HERE");

var result = await client.ScrubNumbersAsync(
    new[] { "3105551212", "3105551213", "3105551214" },
    new[] { "dnc", "state", "litigator" }
);

Console.WriteLine(result.RootElement);
POST /api/v2/scrubs .NET 8+ request
var client = new DncProjectClient("YOUR_TOKEN_HERE");

var result = await client.ScrubNumbersAsync(
    new[] { "3105551212", "3105551213", "3105551214" },
    new[] { "dnc", "state", "litigator" }
);

Console.WriteLine(result.RootElement);
POST /api/v2/scrubs C++11 request
curl_global_init(CURL_GLOBAL_DEFAULT);
DncProjectClient client("YOUR_TOKEN_HERE");

std::string result = client.scrubNumbers(
    std::vector<std::string>{"3105551212", "3105551213", "3105551214"},
    std::vector<std::string>{"dnc", "state", "litigator"}
);

curl_global_cleanup();
POST /api/v2/scrubs C++17 request
curl_global_init(CURL_GLOBAL_DEFAULT);
DncProjectClient client("YOUR_TOKEN_HERE");

std::string result = client.scrubNumbers(
    std::vector<std::string>{"3105551212", "3105551213", "3105551214"},
    std::vector<std::string>{"dnc", "state", "litigator"}
);

curl_global_cleanup();
POST /api/v2/scrubs C++20 request
curl_global_init(CURL_GLOBAL_DEFAULT);
DncProjectClient client("YOUR_TOKEN_HERE");

std::string result = client.scrubNumbers(
    std::vector<std::string>{"3105551212", "3105551213", "3105551214"},
    std::vector<std::string>{"dnc", "state", "litigator"}
);

curl_global_cleanup();
POST /api/v2/scrubs JSON response example
{
  "result": [
    {
      "number": "3105551212",
      "is_dnc": true,
      "state_tx": true,
      "is_litigator": false
    },
    {
      "number": "3105551213",
      "is_dnc": false,
      "state_tx": false,
      "is_litigator": false
    }
  ]
}
POST /api/v2/scrub/file PHP 5.4 upload request
$client = new DncProjectClient('YOUR_TOKEN_HERE');

$result = $client->uploadFile(
    __DIR__ . '/leads.csv',
    array('dnc', 'state'),
    array(0),
    'https://your-domain.com/dnc-webhook'
);
POST /api/v2/scrub/file PHP 7.0 upload request
$client = new DncProjectClient('YOUR_TOKEN_HERE');

$result = $client->uploadFile(
    __DIR__ . '/leads.csv',
    ['dnc', 'state'],
    [0],
    'https://your-domain.com/dnc-webhook'
);
POST /api/v2/scrub/file PHP 7.4 upload request
$client = new DncProjectClient('YOUR_TOKEN_HERE');

$result = $client->uploadFile(
    __DIR__ . '/leads.csv',
    ['dnc', 'state'],
    [0],
    'https://your-domain.com/dnc-webhook'
);
POST /api/v2/scrub/file PHP 8.0 upload request
$client = new DncProjectClient('YOUR_TOKEN_HERE');

$result = $client->uploadFile(
    __DIR__ . '/leads.csv',
    ['dnc', 'state'],
    [0],
    'https://your-domain.com/dnc-webhook'
);
POST /api/v2/scrub/file PHP 8.1 upload request
$client = new DncProjectClient('YOUR_TOKEN_HERE');

$result = $client->uploadFile(
    __DIR__ . '/leads.csv',
    ['dnc', 'state'],
    [0],
    'https://your-domain.com/dnc-webhook'
);
POST /api/v2/scrub/file PHP 8.2 upload request
$client = new DncProjectClient('YOUR_TOKEN_HERE');

$result = $client->uploadFile(
    __DIR__ . '/leads.csv',
    ['dnc', 'state'],
    [0],
    'https://your-domain.com/dnc-webhook'
);
POST /api/v2/scrub/file PHP 8.3 upload request
$client = new DncProjectClient('YOUR_TOKEN_HERE');

$result = $client->uploadFile(
    __DIR__ . '/leads.csv',
    ['dnc', 'state'],
    [0],
    'https://your-domain.com/dnc-webhook'
);
POST /api/v2/scrub/file PHP 8.4 upload request
$client = new DncProjectClient('YOUR_TOKEN_HERE');

$result = $client->uploadFile(
    __DIR__ . '/leads.csv',
    ['dnc', 'state'],
    [0],
    'https://your-domain.com/dnc-webhook'
);
POST /api/v2/scrub/file PHP 8.5 upload request
$client = new DncProjectClient('YOUR_TOKEN_HERE');

$result = $client->uploadFile(
    __DIR__ . '/leads.csv',
    ['dnc', 'state'],
    [0],
    'https://your-domain.com/dnc-webhook'
);
POST /api/v2/scrub/file ES5 upload request
var client = new DncProjectClient('YOUR_TOKEN_HERE');
var file = document.getElementById('csv').files[0];

client.uploadFile(file, {
    scrubTypes: ['dnc', 'state'],
    columns: [0],
    webhookUrl: 'https://your-domain.com/dnc-webhook'
}, function (error, result) {
    if (error) {
        throw error;
    }

    console.log(result);
});
POST /api/v2/scrub/file ES2017+ upload request
const client = new DncProjectClient('YOUR_TOKEN_HERE');
const file = document.querySelector('#csv').files[0];

const result = await client.uploadFile(file, {
    scrubTypes: ['dnc', 'state'],
    columns: [0],
    webhookUrl: 'https://your-domain.com/dnc-webhook',
});
POST /api/v2/scrub/file Python 2.7 upload request
client = DncProjectClient('YOUR_TOKEN_HERE')

result = client.upload_file(
    'leads.csv',
    scrub_types=('dnc', 'state'),
    columns=(0,),
    webhook_url='https://your-domain.com/dnc-webhook',
)
POST /api/v2/scrub/file Python 3.6 upload request
client = DncProjectClient('YOUR_TOKEN_HERE')

result = client.upload_file(
    'leads.csv',
    scrub_types=('dnc', 'state'),
    columns=(0,),
    webhook_url='https://your-domain.com/dnc-webhook',
)
POST /api/v2/scrub/file Python 3.8 upload request
client = DncProjectClient('YOUR_TOKEN_HERE')

result = client.upload_file(
    'leads.csv',
    scrub_types=('dnc', 'state'),
    columns=(0,),
    webhook_url='https://your-domain.com/dnc-webhook',
)
POST /api/v2/scrub/file Python 3.10+ upload request
client = DncProjectClient('YOUR_TOKEN_HERE')

result = client.upload_file(
    'leads.csv',
    scrub_types=('dnc', 'state'),
    columns=(0,),
    webhook_url='https://your-domain.com/dnc-webhook',
)
POST /api/v2/scrub/file Go 1.13 upload request
client := dncproject.Client{
    Token: "YOUR_TOKEN_HERE",
    BaseURL: "https://thedncproject.org",
}

result, err := client.UploadFile(
    context.Background(),
    "leads.csv",
    []string{"dnc", "state"},
    []int{0},
    "https://your-domain.com/dnc-webhook",
)
if err != nil {
    panic(err)
}

fmt.Println(string(result))
POST /api/v2/scrub/file Go 1.16+ upload request
client := dncproject.Client{
    Token: "YOUR_TOKEN_HERE",
    BaseURL: "https://thedncproject.org",
}

result, err := client.UploadFile(
    context.Background(),
    "leads.csv",
    []string{"dnc", "state"},
    []int{0},
    "https://your-domain.com/dnc-webhook",
)
if err != nil {
    panic(err)
}

fmt.Println(string(result))
POST /api/v2/scrub/file Go stable upload request
client := dncproject.Client{
    Token: "YOUR_TOKEN_HERE",
    BaseURL: "https://thedncproject.org",
}

result, err := client.UploadFile(
    context.Background(),
    "leads.csv",
    []string{"dnc", "state"},
    []int{0},
    "https://your-domain.com/dnc-webhook",
)
if err != nil {
    panic(err)
}

fmt.Println(string(result))
POST /api/v2/scrub/file Rust 1.56+ upload request
let client = DncProjectClient::new("YOUR_TOKEN_HERE");

let result = client.upload_file(
    "leads.csv",
    &["dnc", "state"],
    &[0],
    Some("https://your-domain.com/dnc-webhook"),
)?;

println!("{}", serde_json::to_string_pretty(&result)?);
POST /api/v2/scrub/file Rust 1.70+ upload request
let client = DncProjectClient::new("YOUR_TOKEN_HERE");

let result = client.upload_file(
    "leads.csv",
    &["dnc", "state"],
    &[0],
    Some("https://your-domain.com/dnc-webhook"),
)?;

println!("{}", serde_json::to_string_pretty(&result)?);
POST /api/v2/scrub/file Rust stable upload request
let client = DncProjectClient::new("YOUR_TOKEN_HERE");

let result = client.upload_file(
    "leads.csv",
    &["dnc", "state"],
    &[0],
    Some("https://your-domain.com/dnc-webhook"),
)?;

println!("{}", serde_json::to_string_pretty(&result)?);
POST /api/v2/scrub/file Ruby 2.7 upload request
client = DncProjectClient.new('YOUR_TOKEN_HERE')

result = client.upload_file(
  'leads.csv',
  scrub_types: ['dnc', 'state'],
  columns: [0],
  webhook_url: 'https://your-domain.com/dnc-webhook'
)
POST /api/v2/scrub/file Ruby 3.0 upload request
client = DncProjectClient.new('YOUR_TOKEN_HERE')

result = client.upload_file(
  'leads.csv',
  scrub_types: ['dnc', 'state'],
  columns: [0],
  webhook_url: 'https://your-domain.com/dnc-webhook'
)
POST /api/v2/scrub/file Ruby 3.1+ upload request
client = DncProjectClient.new('YOUR_TOKEN_HERE')

result = client.upload_file(
  'leads.csv',
  scrub_types: ['dnc', 'state'],
  columns: [0],
  webhook_url: 'https://your-domain.com/dnc-webhook'
)
POST /api/v2/scrub/file Java 8 upload request
DncProjectClient client = new DncProjectClient("YOUR_TOKEN_HERE");

String result = client.uploadFile(
    Paths.get("leads.csv"),
    Arrays.asList("dnc", "state"),
    Arrays.asList(0),
    "https://your-domain.com/dnc-webhook"
);

System.out.println(result);
POST /api/v2/scrub/file Java 11+ upload request
DncProjectClient client = new DncProjectClient("YOUR_TOKEN_HERE");

String result = client.uploadFile(
    Path.of("leads.csv"),
    List.of("dnc", "state"),
    List.of(0),
    "https://your-domain.com/dnc-webhook"
);

System.out.println(result);
POST /api/v2/scrub/file Java 17+ upload request
DncProjectClient client = new DncProjectClient("YOUR_TOKEN_HERE");

String result = client.uploadFile(
    Path.of("leads.csv"),
    List.of("dnc", "state"),
    List.of(0),
    "https://your-domain.com/dnc-webhook"
);

System.out.println(result);
POST /api/v2/scrub/file .NET Framework 4.7.2 upload request
var client = new DncProjectClient("YOUR_TOKEN_HERE");

var result = await client.UploadFileAsync(
    "leads.csv",
    new[] { "dnc", "state" },
    new[] { 0 },
    "https://your-domain.com/dnc-webhook"
);

Console.WriteLine(result);
POST /api/v2/scrub/file .NET 6+ upload request
var client = new DncProjectClient("YOUR_TOKEN_HERE");

var result = await client.UploadFileAsync(
    "leads.csv",
    new[] { "dnc", "state" },
    new[] { 0 },
    "https://your-domain.com/dnc-webhook"
);

Console.WriteLine(result);
POST /api/v2/scrub/file .NET 8+ upload request
var client = new DncProjectClient("YOUR_TOKEN_HERE");

var result = await client.UploadFileAsync(
    "leads.csv",
    new[] { "dnc", "state" },
    new[] { 0 },
    "https://your-domain.com/dnc-webhook"
);

Console.WriteLine(result);
POST /api/v2/scrub/file C++11 upload request
curl_global_init(CURL_GLOBAL_DEFAULT);
DncProjectClient client("YOUR_TOKEN_HERE");

std::string result = client.uploadFile(
    "leads.csv",
    std::vector<std::string>{"dnc", "state"},
    std::vector<int>{0},
    "https://your-domain.com/dnc-webhook"
);

curl_global_cleanup();
POST /api/v2/scrub/file C++17 upload request
curl_global_init(CURL_GLOBAL_DEFAULT);
DncProjectClient client("YOUR_TOKEN_HERE");

std::string result = client.uploadFile(
    "leads.csv",
    std::vector<std::string>{"dnc", "state"},
    std::vector<int>{0},
    "https://your-domain.com/dnc-webhook"
);

curl_global_cleanup();
POST /api/v2/scrub/file C++20 upload request
curl_global_init(CURL_GLOBAL_DEFAULT);
DncProjectClient client("YOUR_TOKEN_HERE");

std::string result = client.uploadFile(
    "leads.csv",
    std::vector<std::string>{"dnc", "state"},
    std::vector<int>{0},
    "https://your-domain.com/dnc-webhook"
);

curl_global_cleanup();
POST /api/v2/scrub/file JSON response example
{
  "success": true,
  "message": "You will be notified by email when scrub is complete."
}
DNC API overview

What is the DNC API?

The DNC Project API is a REST API for Do Not Call scrubbing. Approved customers can submit phone numbers or uploaded files for National DNC, state DNC, and litigator checks.

Use it when a CRM, dialer, lead platform, compliance tool, or internal workflow needs real-time phone number scrubbing before outbound calls or texts.

The examples above include PHP, JavaScript, Python, Go, Rust, Ruby, Java, C# .NET, and C++ integrations for common backend and automation workflows, including older runtimes such as PHP 5.4, ES5, Python 2.7, Java 8, and .NET Framework 4.7.2.

POST /api/v2/scrubs Array of Numbers

Scrub an array of U.S. phone numbers in real time and return a JSON result array.

POST /api/v2/scrub/file File Upload

Upload a CSV, TSV, plain text, or modern Excel file and receive the scrubbed export when processing finishes.

Related DNC API resources

DNC API pricing, compliance, and scrub coverage

Use these guides when deciding which scrub checks to enable for a real-time API integration or bulk upload workflow.

API contract

OpenAPI summary

The v2 DNC API uses HTTPS, Bearer token authentication, JSON for number requests, and multipart form data for file uploads.

How do I authenticate with the DNC API?

Send your token in the Authorization header using the Bearer scheme.

Authorization: Bearer YOUR_TOKEN_HERE

How do I scrub phone numbers with the DNC API?

POST JSON to /api/v2/scrubs with a numbers array and a required scrub_type array.

How do I upload a CSV file to the DNC API?

POST multipart form data to /api/v2/scrub/file with file, scrub_type[], scrub_column[], and optional webhook_url.

What are the DNC API request limits?

For predictable latency, split large JSON number requests into batches around 3,000 records. File uploads accept CSV, TSV, plain text, and modern Excel files.

What scrub types does the DNC API support?

dnc Checks phone numbers against the National Do Not Call Registry.
state Checks available state Do Not Call lists on enabled accounts.
litigator Flags known TCPA litigators and serial plaintiffs.

What response fields does the DNC API return?

number The normalized phone number returned for each submitted record.
is_dnc Boolean value indicating whether the number matched the National Do Not Call Registry.
state_* State-specific DNC match fields such as state_tx when state scrubbing is enabled.
is_litigator Boolean value indicating whether the number matched the litigator database.

What errors can the DNC API return?

401 Missing, invalid, or expired API token.
403 The API token is valid but does not have access to the requested scrub option.
422 The request payload is missing required fields or includes invalid values.
429 The request rate is too high for the account or endpoint.
DNC API FAQ

Frequently asked questions

What is the DNC API?

The DNC Project API is a REST API for Do Not Call scrubbing. It lets approved customers submit phone numbers or uploaded files for National DNC, state DNC, and litigator checks.

How do I authenticate with the DNC API?

Send your API token in the Authorization header as a Bearer token. The same token format is used for number requests and file upload requests.

How do I scrub phone numbers with the DNC API?

Send a JSON POST request to /api/v2/scrubs with a numbers array and scrub_type values such as dnc, state, and litigator when those options are enabled on your token.

How do I upload a CSV file to the DNC API?

Send a multipart POST request to /api/v2/scrub/file with the file, scrub_type[], scrub_column[], and optional webhook_url fields.

What scrub types does the DNC API support?

The v2 API supports dnc, state, and litigator scrub options when those options are enabled on the account token. Mobile/landline scrubbing is not available through API subscriptions. For a soft migration, legacy requests containing mobile continue with the remaining supported options and omit mobile output.

Copied to clipboard