luchaninov/nicnames-client

PHP client for the Nicnames domain registrar REST API v2

Maintainers

Package info

github.com/luchaninov/nicnames-client

pkg:composer/luchaninov/nicnames-client

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

1.0.3 2026-05-01 21:48 UTC

This package is auto-updated.

Last update: 2026-05-01 21:48:25 UTC


README

PHP client for the Nicnames domain registrar REST API v2. Full API reference: api.nicnames.com/docs/2/.

Disclaimer: This is an unofficial, personal project and is not affiliated with, endorsed by, or maintained by Nicnames. Use at your own risk.

Requirements

  • PHP 8.4+
  • symfony/http-client

Installation

composer require luchaninov/nicnames-client

API key

Generate an API key at nicnames.com/en/my/settings. A paid Nicnames membership is required to access the API.

Usage

use Luchaninov\NicnamesClient\HttpTransport;
use Luchaninov\NicnamesClient\NicnamesClient;
use Symfony\Component\HttpClient\HttpClient;

$transport = new HttpTransport(HttpClient::create(), apiKey: 'YOUR-API-KEY');
$client = new NicnamesClient($transport);

$domains = $client->listDomains();
foreach ($domains->list as $order) {
    echo $order->domain?->name . ' β€” ' . implode(',', $order->status) . PHP_EOL;
}

Automatic retries

HttpTransport accepts any Symfony\Contracts\HttpClient\HttpClientInterface, so transient failures (5xx, network blips) can be handled by wrapping with Symfony's RetryableHttpClient:

use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\HttpClient\RetryableHttpClient;

$http = new RetryableHttpClient(HttpClient::create(), maxRetries: 3);
$transport = new HttpTransport($http, apiKey: 'YOUR-API-KEY');

Domain info & availability

$order = $client->infoDomain('example.com');
echo $order->domain?->name;

$check = $client->checkDomain('example.com');
// $check->availableFor: NONE|CREATE|TRANSFER|RENEW|RESTORE|UPDATE
// $check->tier:         REGULAR|PREMIUM|UNKNOWN
// $check->price:        PriceModel[] β€” one entry per period

Create a domain

Operations that the API may complete inline (200/201) or schedule as a background job (202) return a DomainOperationResult wrapper:

use Luchaninov\NicnamesClient\Dto\CreateDomainRequest;
use Luchaninov\NicnamesClient\Dto\OperationModel;
use Luchaninov\NicnamesClient\Dto\PeriodModel;
use Luchaninov\NicnamesClient\Dto\PeriodUnitModel;
use Luchaninov\NicnamesClient\Dto\PriceModel;

$price = new PriceModel(
    amt: 12.34,
    ccy: 840,
    op: OperationModel::CREATE,
    period: new PeriodModel(PeriodUnitModel::YEARS, 1),
);

$result = $client->createDomain('example.com', new CreateDomainRequest(
    price: $price,
    registrant: 'c987654321',
));

if ($result->isAsync()) {
    echo "Scheduled as job {$result->jobId}\n";
} else {
    echo "Created order {$result->order->oid}\n";
}

The same wrapper is returned by transferDomain(), renewDomain(), restoreDomain(), updateDomainNameServers(), and updateDomainWhoisPrivacy().

Contacts

use Luchaninov\NicnamesClient\Dto\CreateContactRequest;

$contact = $client->createContact(new CreateContactRequest(
    firstName: 'John',
    lastName: 'Doe',
    cc: 'us',
    pc: '62704',
    sp: 'IL',
    city: 'Springfield',
    addr: '123 Main Street',
    email: 'john.doe@example.com',
    phone: '+15551234567',
    phonePolicy: true,
));

$contacts = $client->listContacts();
$one = $client->infoContact('c987654321');

Update nameservers / WHOIS privacy

use Luchaninov\NicnamesClient\Dto\UpdateNameServersRequest;
use Luchaninov\NicnamesClient\Dto\UpdateWhoisPrivacyRequest;

$client->updateDomainNameServers(
    'example.com',
    new UpdateNameServersRequest(['ns1.example.com', 'ns2.example.com']),
);
$client->updateDomainWhoisPrivacy('example.com', new UpdateWhoisPrivacyRequest(
    registrant: UpdateWhoisPrivacyRequest::ENABLE,
));

Webhooks

The API delivers asynchronous results and events as application/x-www-form-urlencoded POSTs containing object (JSON), timestamp, and signature fields. The simplest integration uses WebhookHandler, which verifies the HMAC-SHA256 signature, checks that the timestamp is fresh (default 5 min anti-replay window), and decodes the JSON into a typed event in one call:

use Luchaninov\NicnamesClient\Dto\WebhookJobResultEvent;
use Luchaninov\NicnamesClient\Webhook\WebhookException;
use Luchaninov\NicnamesClient\Webhook\WebhookHandler;
use Luchaninov\NicnamesClient\Webhook\WebhookVerifier;

$handler = new WebhookHandler(new WebhookVerifier(secret: 'YOUR-WEBHOOK-SECRET'));

try {
    $event = $handler->handle($_POST);
} catch (WebhookException) {
    http_response_code(401);
    exit;
}

if ($event instanceof WebhookJobResultEvent) {
    // $event->jobId, $event->code (e.g. 441000 SUCCESS, 442xxx error codes)
}

In a Symfony controller, pass the request payload as an array:

use Luchaninov\NicnamesClient\Dto\WebhookJobResultEvent;
use Luchaninov\NicnamesClient\Webhook\WebhookException;
use Luchaninov\NicnamesClient\Webhook\WebhookHandler;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

#[Route('/webhooks/nicnames', methods: ['POST'])]
public function __invoke(Request $request, WebhookHandler $handler): Response
{
    try {
        $event = $handler->handle($request->request->all());
    } catch (WebhookException) {
        return new Response('', Response::HTTP_UNAUTHORIZED);
    }

    if ($event instanceof WebhookJobResultEvent) {
        // $event->jobId, $event->code
    }

    return new Response('', Response::HTTP_NO_CONTENT);
}

If you need finer control, WebhookVerifier::isValid() and WebhookEventFactory::fromJson() are public and composable.

Error handling

Every API result code maps to a typed exception:

Code Exception
442001 RemoteException
442002 ApiException
442003 ForbiddenException
442006 UnknownStatusException
442007 NotFoundException
442008 StatusProhibitedException
442009 InvalidParamPolicyException
442010 InvalidParamValueException
442011 ParamRequiredException
442012 UnauthorizedException
other NicnamesException

All exceptions carry the API trace id:

use Luchaninov\NicnamesClient\Exception\NicnamesException;

try {
    $client->createDomain('bad', $request);
} catch (NicnamesException $e) {
    $e->getCode();   // 442010
    $e->getMessage(); // 'Invalid domain name.'
    $e->traceId;      // 'c4f9d5b3-...'
}

HTTP / network failures throw TransportException. A 2xx response that doesn't conform to the spec (e.g. an HTTP 202 without a jobId) throws MalformedResponseException.

Webhook validation failures (missing fields, bad signature, stale timestamp, malformed JSON) throw Webhook\WebhookException β€” a separate hierarchy from NicnamesException, since they originate in your inbound traffic, not from the API.

Development

composer install
vendor/bin/phpunit
vendor/bin/phpstan analyze

Manual smoke test

Copy .env.local.example to .env.local (gitignored) and fill in your API key. Then run:

php examples/test.php

Bulk scripts

All scripts read from --input <file>, support --resume, and emit TSV to stdout.

php examples/check.php    --input domains.txt > availability.tsv
php examples/register.php --input domains.txt --registrant c987654321 --term 1 > registered.tsv
php examples/renew.php    --input domains.txt --term 1 > renewed.tsv
php examples/transfer.php --input transfers.tsv --registrant c987654321 --term 1 > transferred.tsv
php examples/ns.php       --input ns.tsv > ns-results.tsv
php examples/privacy.php  --input domains.txt --on > privacy-on.tsv
php examples/privacy.php  --input domains.txt --off --apply-to-all > privacy-off.tsv

# Resume any script after interruption (skip already-processed entries)
php examples/register.php --input domains.txt --registrant c987654321 --resume last-done.com >> registered.tsv

License

MIT

Help Ukraine

If you find this project useful, please consider supporting Ukraine: πŸ‡ΊπŸ‡¦ Donate