kirora / kirora-php
Kirora SDK for PHP.
Requires
- php: ^8.1
- ext-json: *
- guzzlehttp/guzzle: ^7.8.2 || ^8.0
- guzzlehttp/psr7: ^2.6.3 || ^3.0
- psr/http-client: ^1.0
- psr/http-message: ^1.1 || ^2.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.64
- pestphp/pest: ^2.36 || ^3.0
- phpstan/phpstan: ^2.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Use the Kirora API from PHP to manage contacts, track events, build segments, and send campaigns and transactional messages.
PHP guide · API reference · Usage guide · Changelog
- Models generated from Kirora's OpenAPI specification.
- Automatic pagination and retries, with idempotency keys for supported operations.
- Webhook verification, user token signing, and partner workspace support.
- Works with any framework, or none. Sends requests with Guzzle by default, or with any PSR-18 client.
Contents
- Installation
- Quickstart
- Usage
- Pagination
- Client configuration
- Errors
- Retries and idempotency
- More guides
- Troubleshooting
- Compatibility, help, and development
Installation
Requires PHP 8.1 or later.
composer require kirora/kirora-php
Create an API key under Developer → Server keys in the Kirora dashboard, with the scopes your integration needs. Set it in your server's environment:
export KIRORA_API_KEY="your_api_key"
Keep the key on your server and out of source control. The SDK checks getenv() first,
then falls back to $_ENV and $_SERVER when the process variable is missing or empty.
It does not load .env files automatically. If your framework loads configuration
another way, pass the key to Kirora::client($apiKey). For browser
and mobile apps, use
server-issued user tokens.
Quickstart
Save this as quickstart.php beside your project's vendor/ directory. It creates or
updates a contact; replace the external ID and email with your own test values.
<?php require __DIR__ . '/vendor/autoload.php'; $kirora = Kirora::client(); // Reads KIRORA_API_KEY. $contact = $kirora->contacts->identify([ 'externalId' => 'php-quickstart-user', 'email' => 'ada@example.com', ]); echo "Contact saved: {$contact->id}\n";
Run it with the environment variable set:
php quickstart.php
A successful request prints Contact saved: <contact ID>. To check your credentials
without creating data, replace the contact operation with:
$caller = $kirora->me(); echo $caller->workspace['name'], "\n";
In namespaced application code, use \Kirora::client() or import the global Kirora
class. The factory creates a Kirora\Client; no framework-specific facade is required.
Usage
The snippets below use $kirora from the quickstart. Create one client and reuse it
across the operations in your request or worker.
Contacts
identify() creates or updates a contact, matching by external ID, then email, then
phone. Look a contact up by Kirora's ID, or pass idType to use your own:
$contact = $kirora->contacts->get('ada@example.com', ['idType' => 'email']); echo $contact->id;
idType can be 'email' or 'externalId'; leave it out for a Kirora ID. It is a query
parameter, while timeout and maxRetries are call options. For methods that take both,
the query array comes first:
$contact = $kirora->contacts->get( 'php-quickstart-user', ['idType' => 'externalId'], ['timeout' => 5, 'maxRetries' => 0], );
Leave a field out of an update to keep its value. Set a nullable field to null to
clear it; null is sent to the API and does not mean “leave unchanged.”
A contact's lists, segments, topic preferences, and push installations have their own
services: contacts->lists, contacts->segments, contacts->topics, and
contacts->installations. For example, list the quickstart contact's memberships:
foreach ($kirora->contacts->lists->all($contact->id) as $membership) { echo $membership->id, "\n"; }
These methods also accept an idType query. Topics return an array from list();
lists, segments, and installations support paginated list() and all(). See
contact services.
Events
Track an event for a contact that already exists:
$kirora->events->track([ 'externalId' => 'php-quickstart-user', 'name' => 'Order Completed', 'properties' => ['orderId' => '1234', 'total' => 49.99, 'currency' => 'GBP'], ], ['idempotencyKey' => 'order-1234-completed']);
Derive the idempotency key from the operation in your application, and reuse it when retrying that same operation with the same body.
Transactional messages
Complete your workspace's email sending setup and configure an email template first.
Replace order-receipt with your template's alias or ID and use a recipient you control.
$sent = $kirora->transactional->send([ 'to' => ['email' => 'customer@example.com'], 'data' => ['orderId' => '1234'], 'email' => ['template' => 'order-receipt'], ], ['idempotencyKey' => 'receipt-1234']); echo count($sent->messages), " message(s) queued\n";
A successful call can leave a requested channel unsent. Inspect skippedChannels,
for example when a contact has no push device:
foreach ($sent->skippedChannels as $skipped) { echo $skipped->channel, ': ', $skipped->code, "\n"; }
Likewise, a cc or bcc address can get no copy, for example when it's suppressed.
Inspect skippedCopies:
foreach ($sent->skippedCopies as $skipped) { echo $skipped->kind, ' ', $skipped->email, ': ', $skipped->code, "\n"; }
contacts->identifyBatch(), events->trackBatch() and transactional->sendBatch() accept
up to 100 entries. A successful call can contain failed entries: inspect each entry in
$batch->results for an error. For event and transactional batches, keys belong to
individual entries. Set each entry's idempotencyKey when retrying from your application;
see batch requests.
Services
| Service | Use it to |
|---|---|
contacts |
Identify, update, merge, export, and delete contacts; manage preferences and push devices; list their segments. |
events |
Track individual events or batches. |
transactional |
Send messages individually or in batches; cancel scheduled messages. |
campaigns |
Create campaigns, check eligibility, send, and schedule. |
segments |
Create and preview segments; list their contacts and see their size over time. |
lists |
Create lists and manage membership. |
messages |
Inspect sent messages. |
suppressions |
Manage suppressed email addresses. |
workspaces |
Manage tenant workspaces with a partner key. |
embedSessions |
Create sessions for embedded Kirora builders. |
Requests take arrays, as the API reference
describes them. Responses are models from Kirora\Models: read fields as properties,
$contact->email, or as array keys, $contact['email']. Your editor completes them from
each model's documented fields. Models are read-only; use toArray() or toJson() to
serialize them. Unknown response fields are preserved. See models.
Pagination
Use all() to iterate every matching item, fetching additional pages as needed:
foreach ($kirora->contacts->all(['query' => 'ada']) as $contact) { echo $contact->email, "\n"; }
all() makes its first request when iteration starts. Use break to stop fetching.
Use list() for one page; iterating that page does not fetch subsequent pages:
$page = $kirora->contacts->list(['query' => 'ada', 'limit' => 50]); foreach ($page as $contact) { echo $contact->id, "\n"; } if ($page->hasMore()) { $next = $kirora->contacts->list([ 'query' => 'ada', 'limit' => 50, 'cursor' => $page->nextCursor(), ]); }
Keep the same filters when following a cursor. nextCursor() returns null on the last
page; an empty page alone does not mean pagination is finished. See pagination details.
Client configuration
Create one client and reuse it. Pass an API key to Kirora::client(), or nothing to read
KIRORA_API_KEY:
$kirora = Kirora::client(null, [ 'timeout' => 10, 'maxRetries' => 2, ]);
| Option | Default | Purpose |
|---|---|---|
timeout |
30 seconds | Seconds per attempt with Guzzle, including reading the response. |
maxRetries |
3 | Allow 0–10 retries after the first attempt. |
maxRetryDelay |
30 seconds | Limit the wait between attempts; a longer Retry-After stops retries. |
baseUrl |
KIRORA_BASE_URL or https://api.kirora.io |
Set the API host, without /v1. |
httpClient |
A new Guzzle client | Use your own PSR-18 client, such as one with a proxy. |
workspace |
None | Scope a partner key to a tenant workspace. |
headers |
None | Add headers to every request. |
appInfo |
None | Add your app to the User-Agent, as ['name' => ..., 'version' => ...]. |
Every method takes call options as its last argument. timeout, maxRetries,
maxRetryDelay, workspace and headers override the client's setting for that call,
and idempotencyKey is per call only. For methods with a query argument, pass [] to
skip the query before supplying call options. See configuration details.
Retries and backoff can make a call take longer than timeout. A custom PSR-18 client
that does not implement Guzzle's client interface must have its timeouts configured on
that client; the SDK cannot enforce its timeout option there. requestRaw() returns an
unread response body, so its timeout covers connecting and individual reads rather than
consuming the whole stream.
Errors
API errors throw Kirora\Exceptions\ErrorException:
use Kirora\Enums\ErrorCode; use Kirora\Exceptions\ErrorException; use Kirora\Exceptions\TransporterException; try { $contact = $kirora->contacts->get('php-quickstart-user', ['idType' => 'externalId']); } catch (TransporterException $error) { // Automatic retries have finished, or the operation could not be safely retried. error_log($error->getMessage()); throw $error; } catch (ErrorException $error) { if ($error->is(ErrorCode::NotFound)) { return null; // Handle the missing contact in your application. } error_log("Kirora error: {$error->errorCode} status={$error->status} request_id={$error->requestId}"); throw $error; }
ErrorException includes the API code, HTTP status, request ID, field errors, and
available rate-limit details. Include the request ID when reporting a failure. Network
failures throw TransporterException (or TimeoutException, a subclass); responses the
SDK can't use throw UnserializableResponse. All of them implement
Kirora\Exceptions\KiroraException.
Invalid local arguments throw PHP's InvalidArgumentException before a request is
sent; that exception does not implement KiroraException.
See error handling and the API error codes.
Retries
The SDK allows up to 3 retries by default, with exponential backoff and jitter:
- Rate limits (429) and failures that never established a connection can be retried.
- Other network failures and 5xx responses are retried only when repeating the operation is safe.
IDEMPOTENCY_KEY_IN_USE(409) can be retried when the call has a key.- Other 4xx responses are not retried.
Pass 'maxRetries' => 0 to disable retries. See the retry guide
for operation-specific rules.
Idempotency keys
For supported operations, the SDK generates an idempotency key per call and reuses it for
its own retries. Separate calls get separate keys. To deduplicate retries from your
application, pass a stable idempotencyKey to each call for the same operation and body,
as in the event and transactional examples above.
More guides
The usage guide covers each of these in detail.
Webhooks
Verify the raw request body with Kirora\Webhook::verify() before decoding it:
use Kirora\Webhook; $secret = getenv('KIRORA_WEBHOOK_SECRET'); if ($secret === false || $secret === '') { http_response_code(500); // Configure the endpoint's signing secret on the server. exit; } $rawBody = file_get_contents('php://input'); if ($rawBody === false) { http_response_code(400); exit; } $webhook = Webhook::verify($secret, $rawBody, getallheaders()); if ($webhook === null) { http_response_code(401); exit; } // Handle $webhook->payload; deduplicate on $webhook->payload['eventId'].
Webhooks can arrive more than once; deduplicate on the event ID, and record an event as handled only once its work has succeeded, so a failed delivery is handled again on retry. See verifying webhooks.
Authenticate browser and mobile SDK users
Create user tokens on your server with Kirora\UserToken::create(), using your SDK token
secret and the authenticated user's ID from your server session, never an ID supplied
by the browser. The SDK token secret is separate from your API key. See
user token signing.
Partner workspaces
Use $kirora->forWorkspace($externalId) with a partner key to scope calls to a tenant.
See partner workspaces.
Advanced requests
Use request() for endpoints without a service method, requestAll() for paginated
results, and requestRaw() to stream a response. Models and pages expose response
metadata through lastResponse(): request ID, rate limit and idempotency replay. See
advanced requests.
Troubleshooting
| Problem | Check |
|---|---|
| API key is missing | Set KIRORA_API_KEY in the PHP process environment, or pass the key explicitly. A .env file alone is not loaded by the SDK. |
API returns 401 |
Check whether the key is valid, expired or revoked. |
API returns 403 |
Check the endpoint's required scopes, accepted key kinds and partner workspace requirements. |
| A timeout override has no effect | Put it in the call-options array, after any query array. Configure timeouts directly on non-Guzzle PSR-18 clients. |
| Only one page is returned | Use all() for automatic pagination; list() returns one page. |
| A batch succeeds but some entries fail | Inspect each entry in results for an error. |
| Webhook verification fails | Use the endpoint's signing secret and the unchanged raw body. See verification diagnostics. |
Compatibility
- PHP: 8.1 or later.
- API: targets
/v1. Unknown enum values and error codes are kept as strings; useErrorCode::isKnown()andWebhookEvent::isKnown()to check whether your SDK version recognizes a value. Fields this SDK has no model property for are kept too, so nothing the API sends is lost. - Releases: before 1.0, minor releases may include breaking changes. Review the changelog when upgrading.
Help
For SDK bugs or feature requests, open an issue. Include your SDK and PHP versions, a minimal reproduction, and the request ID when available. Remove API keys, tokens, and personal data before sharing logs.
For integration help, email support@kirora.io or start with the PHP guide, the API reference, or the usage guide.
Development
composer install
composer test
See CONTRIBUTING.md for OpenAPI code generation and the full set of checks. The test suite requires PHP 8.2 or later; using the SDK requires PHP 8.1 or later.