PHP SDK
PHP client for the ScrapingIsNotACrime API over any PSR-18 HTTP client, with responses as final readonly classes.
01Install
If your project has no PSR-18 client or PSR-17 factory yet, Composer's php-http/discovery plugin installs one. Composer asks once whether to allow the plugin; answer yes. If discovery finds some other PSR-18 client (not Symfony HttpClient or Guzzle), the SDK uses it as found, so that client's own timeout and redirect behavior apply; see โBring your own HTTP clientโ.
composer require scrapingisnotacrime/sdk
Or pick the implementation yourself:
composer require scrapingisnotacrime/sdk symfony/http-client nyholm/psr7
02Quick start
use ScrapingIsNotACrime\Client;
use ScrapingIsNotACrime\Exception\NotFoundException;
$client = new Client(apiKey: 'sinac_...');
try {
$profile = $client->instagram->profile('nasa');
echo $profile->username, ' ', $profile->followers, PHP_EOL;
} catch (NotFoundException) {
echo 'no such profile', PHP_EOL;
}Get a key at your dashboard. Keys start with sinac_, and new accounts get 100 free credits.
new Client() with no arguments reads SCRAPINGISNOTACRIME_API_KEY from the environment.
03Configuration
$client = new Client(
apiKey: 'sinac_...',
baseUrl: 'https://api.scrapingisnotacrime.com/v1',
timeout: 30.0,
maxRetries: 2,
httpClient: null,
requestFactory: null,
);| Option | Default | Description |
|---|---|---|
apiKey | SCRAPINGISNOTACRIME_API_KEY | Your API key. The constructor throws \InvalidArgumentException if none is set. Defaults to the SCRAPINGISNOTACRIME_API_KEY environment variable. |
baseUrl | https://api.scrapingisnotacrime.com/v1 | API base URL. Redirects are not followed, so use the final HTTPS URL. |
timeout | 30.0 | Per attempt, in seconds. Applied only when the SDK builds the HTTP client itself. |
maxRetries | 2 | Extra attempts for 429, 502 and network errors. 0 disables retries. |
httpClient | โ | Your own PSR-18 client, used as is. By default, the SDK builds a client itself. |
requestFactory | โ | Your own PSR-17 request factory. By default, discovered via php-http/discovery. |
04Methods
Methods are grouped by platform under $client-><platform> and return typed objects; paginated ones return a Page. Optional arguments default to null, meaning the API's default.
05Pagination
Page has items, hasMore, nextCursor or nextPage, data and next(). A foreach over it fetches later pages lazily.
Each page fetched is one billed request, so bound your loops. Breaking out early stops fetching.
$page = $client->bluesky->posts('bsky.app', limit: 25);
$fetched = 0;
while ($page !== null && $fetched < 3) {
foreach ($page->items as $post) {
echo $post->text, PHP_EOL;
}
$page = $page->next();
$fetched++;
}$page = $client->github->followers('torvalds', limit: 100);
$count = 0;
foreach ($page as $user) {
echo $user->username, PHP_EOL;
if (++$count >= 250) {
break; // stop early; no further pages are fetched
}
}06Errors
Every failure is an exception extending ScrapingIsNotACrimeException with ->status and ->requestId. Invalid arguments throw \InvalidArgumentException before any request. If no PSR-18 client or PSR-17 factory can be found, the constructor throws \LogicException.
| Type | Status | Retried |
|---|---|---|
BadRequestException | 400 | no |
AuthenticationException | 401 | no |
QuotaExceededException | 402 | no |
NotFoundException | 404 | no |
RateLimitException | 429 | yes |
UpstreamException | 502 | yes |
ConnectionException | network failure or per-attempt timeout | yes |
ApiException | any other status, a 2xx without the JSON envelope, a redirect, or data of an unexpected shape | no |
use ScrapingIsNotACrime\Exception\NotFoundException;
use ScrapingIsNotACrime\Exception\QuotaExceededException;
use ScrapingIsNotACrime\Exception\RateLimitException;
use ScrapingIsNotACrime\Exception\ScrapingIsNotACrimeException;
try {
$profile = $client->tiktok->profile('this-user-does-not-exist-123');
echo $profile->username, PHP_EOL;
} catch (NotFoundException) {
echo 'no such profile', PHP_EOL;
} catch (QuotaExceededException $e) {
throw $e; // includes the pricing link in the message
} catch (RateLimitException) {
echo 'TikTok is rate limiting; already retried, try again later', PHP_EOL;
} catch (ScrapingIsNotACrimeException $e) {
throw $e;
}07Retries
Rate limits (429), upstream failures (502) and network failures or timeouts are retried automatically, up to 2 extra attempts by default. These failures don't consume credits. The wait honors the Retry-After header; otherwise it is exponential backoff with jitter, starting at 500 ms and capped at 10 seconds.
Pass maxRetries: 0 to disable retries.
08Bring your own HTTP client
Pass httpClient for tests, proxies or connection pooling. The SDK never modifies it, so set its timeout and turn off redirects yourself.
use GuzzleHttp\Client;
use ScrapingIsNotACrime\Client as ScrapingIsNotACrimeClient;
$client = new ScrapingIsNotACrimeClient(
apiKey: 'sinac_...',
httpClient: new Client([
'timeout' => 30,
'allow_redirects' => false,
// 'proxy' => 'http://localhost:8080',
]),
);use ScrapingIsNotACrime\Client as ScrapingIsNotACrimeClient;
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\HttpClient\Psr18Client;
$client = new ScrapingIsNotACrimeClient(
apiKey: 'sinac_...',
httpClient: new Psr18Client(HttpClient::create([
'timeout' => 30,
'max_duration' => 30,
'max_redirects' => 0,
// 'proxy' => 'http://localhost:8080',
])),
);