Package contract
The planned Composer package is nordicdevhouse/sdk under the NordicDevhouse\Sdk namespace. It follows PSR-4, accepts PSR-18 transports, and exposes immutable value objects.
| Distribution | Namespace or import | Runtime |
|---|---|---|
| nordicdevhouse/sdk | NordicDevhouse\Sdk | PHP 8.2+ and PSR-compatible runtimes |
Create and reuse the client
The client is framework-neutral. A factory discovers installed PSR implementations, while explicit constructor injection keeps transport and clock behaviour testable.
use NordicDevhouse\Sdk\Client;
use NordicDevhouse\Sdk\ScrapeRequest;
$client = Client::fromApiKey(
$_ENV['NORDIC_DEVHOUSE_API_KEY'],
maxRetries: 3,
);
$result = $client->scrape()->run(new ScrapeRequest(
url: 'https://example.com/products',
formats: ['json'],
idempotencyKey: 'catalog-2026-08-11',
));
echo $result->jobId;Control a durable job
Start and wait model durable asynchronous server work without requiring promises in the public interface. Queue workers persist the returned job ID between calls.
$job = $client->scrape()->start(new ScrapeRequest(
url: 'https://example.com/products',
formats: ['json'],
));
$result = $client->jobs()->wait(
id: $job->id,
timeout: new DateInterval('PT10M'),
);Public models
Names follow the language conventions, but every SDK preserves the same fields and job-state semantics.
| Concept | Contract |
|---|---|
| Client options | API key, base URL, request timeout, retry budget, and optional test transport |
| Scrape request | URL, output formats, webhook URL, metadata, and idempotency key |
| Job | Stable ID, status, timestamps, progress, and warnings |
| Job result | Job ID, record count, typed data, metadata, and request ID |
| Webhook event | Verified event ID, type, timestamp, and typed data |
Handle errors idiomatically
Typed exceptions implement a common NordicDevhouseException interface. Retryable exceptions expose Retry-After as a DateInterval and validation exceptions expose field-indexed issues.
try {
return $client->scrape()->run($request);
} catch (RateLimitException $error) {
$logger->warning('Rate limited', [
'request_id' => $error->requestId,
'retry_after' => $error->retryAfter,
]);
throw $error;
} catch (ValidationException $error) {
return validationResponse($error->issues);
} catch (NordicDevhouseException $error) {
$logger->error('SDK failure', ['request_id' => $error->requestId]);
throw $error;
}Verify the raw webhook body
Verification uses the exact request bytes, validates HMAC-SHA256 and timestamp tolerance, and returns a typed event. Persist the verified event ID to prevent duplicate processing.
$event = $client->webhooks()->verify(
payload: (string) $request->getBody(),
signature: $request->getHeaderLine('Nordic-Signature'),
secret: $_ENV['NORDIC_WEBHOOK_SECRET'],
);
if ($event->type === 'scrape.completed') {
importResult($event->data->jobId);
}Runtime and lifecycle rules
- 1Require PHP 8.2 and strict types in package source
- 2Follow PSR-4 and accept PSR-18, PSR-17, and PSR-3 adapters
- 3Keep response value objects immutable and fully annotated for static analysis
- 4Do not require Laravel or Symfony in the core package
- 5Publish framework providers as thin optional adapters