PHP SDK
The Querri PHP SDK (querri/embed) lets you embed Querri analytics in PHP applications. From your server, it creates users, manages access policies, creates embed sessions, and reads and writes data.
Requires PHP 8.3 or later. Built on Symfony HttpClient.
Installation
Section titled “Installation”composer require querri/embedUpgrading from 0.2.x? See Upgrading to 1.0.0.
Quick start
Section titled “Quick start”The shortest path to an embedded analytics session in a PHP backend:
use Querri\Embed\QuerriClient;
$querri = new QuerriClient([ 'api_key' => $_ENV['QUERRI_API_KEY'], // qk_... 'org_id' => $_ENV['QUERRI_ORG_ID'],]);
// Create or find the user, then create their session$session = $querri->getSession([ 'user' => [ 'external_id' => $currentUser->id, 'email' => $currentUser->email, 'first_name' => $currentUser->firstName, 'last_name' => $currentUser->lastName, ], 'origin' => $_SERVER['HTTP_ORIGIN'] ?? null,]);
// Return the token to your frontendecho json_encode(['sessionToken' => $session->sessionToken]);Then render the embed on your frontend with the Embed SDK and that session token.
Authentication
Section titled “Authentication”API keys start with qk_ and belong to one organization. An admin creates them in Settings → API Keys in Querri.
The client needs an API key and an organization ID. Without an organization ID it throws ConfigException straight away, because the API refuses every request that lacks one. Provide them in any of three ways.
A config array
$querri = new QuerriClient([ 'api_key' => 'qk_live_...', 'org_id' => 'your_org_id',]);An API key string (the organization ID comes from the environment)
$querri = new QuerriClient('qk_live_...');// Reads QUERRI_ORG_ID from the environmentEnvironment variables only
export QUERRI_API_KEY="qk_live_..."export QUERRI_ORG_ID="your_org_id"$querri = new QuerriClient(); // reads from the environmentConfiguration options
Section titled “Configuration options”| Option | Environment variable | Default | Description |
|---|---|---|---|
api_key | QUERRI_API_KEY | (required) | Your qk_ API key |
org_id | QUERRI_ORG_ID | (required) | Organization ID |
host | QUERRI_URL | https://app.querri.com | Server URL |
default_origin | QUERRI_EMBED_ORIGIN | none | Origin used for embed sessions when you don’t pass one |
timeout | 30.0 | Request timeout in seconds | |
max_retries | 3 | Retries for failed requests |
Camel-case keys (apiKey, orgId, maxRetries, defaultOrigin) work too.
getSession: the main entry point
Section titled “getSession: the main entry point”getSession() does three things in one call:
- Finds or creates the user by
external_id. - Applies access, creating or reusing an access policy when you pass inline filters.
- Creates the embed session and returns its token.
$session = $querri->getSession([ 'user' => [ 'external_id' => 'user-123', 'email' => 'alice@example.com', 'first_name' => 'Alice', 'last_name' => 'Smith', ], 'access' => [ 'sources' => ['8f14e45f-ceea-4ba1-9f3c-5d2b8a0c1e77'], 'filters' => ['region' => 'US'], ], 'origin' => 'https://app.example.com', 'ttl' => 3600, // seconds, 900 to 86400 (default 3600)]);
$session->sessionToken; // pass to the Embed SDK$session->userId; // Querri's user ID$session->externalId; // your external_id, echoed back$session->expiresIn; // seconds until it expiresA ttl outside 900 to 86400, or an origin longer than 500 characters, throws ValidationException before any request is sent.
The result implements JsonSerializable, so you can pass it straight to json_encode():
header('Content-Type: application/json');echo json_encode($session);// {"session_token":"es_...","expires_in":3600,"user_id":"...","external_id":"user-123"}User shorthand. Pass a string when the user already exists in Querri with that external ID:
$session = $querri->getSession([ 'user' => 'tenant_123', 'origin' => 'https://app.example.com',]);The shorthand sends no email or name, so it can’t create a new user. Pass the array form for users who might not exist yet.
Origins and allowed domains
Section titled “Origins and allowed domains”If your organization has a list of allowed embed domains, every session needs an origin that’s on the list. Otherwise the API returns 400 (origin_required) or 403 (origin_not_allowed), which the SDK throws as ValidationException or PermissionException. Set default_origin (or QUERRI_EMBED_ORIGIN) so sessions get an origin even when a request arrives without an Origin header.
Access options
Section titled “Access options”You can control what the user sees in two ways.
Inline filters. The SDK manages the policy for you:
'access' => [ 'sources' => ['source-uuid-1', 'source-uuid-2'], 'filters' => [ 'region' => 'US', // one value 'department' => ['Sales', 'Marketing'], // several values, matched with OR ],],Policy IDs you’ve already created:
'access' => [ 'policy_ids' => ['policy-uuid-1', 'policy-uuid-2'],],With no access block, the SDK leaves the user’s existing policy assignments as they are.
Access policies
Section titled “Access policies”For more control than getSession() gives you, manage policies directly.
Create a policy
$policy = $querri->policies->create([ 'name' => 'US Region Only', 'description' => 'Only see US rows', 'source_ids' => ['source-uuid'], 'row_filters' => [ ['column' => 'region', 'values' => ['US']], ],]);Assign users
$querri->policies->assignUsers($policy['id'], ['user_ids' => ['user-id-1', 'user-id-2']]);Remove a user
$querri->policies->removeUser($policyId, $userId);Replace every policy assignment for a user
$querri->policies->replaceUserPolicies($userId, ['policy_ids' => ['policy-id-1', 'policy-id-2']]);// Pass an empty list to remove all of themSee what a user can access in a source
$resolved = $querri->policies->resolveAccess($userId, $sourceId);List filterable columns (useful for building a policy screen)
$columns = $querri->policies->listColumns($sourceId);List, retrieve, update, delete
$policies = $querri->policies->list(['name' => 'US Region']);$policy = $querri->policies->retrieve($policyId);$querri->policies->update($policyId, ['name' => 'New Name']);$querri->policies->del($policyId);resolve() and columns() still work but are deprecated. Use resolveAccess() and listColumns().
How policies combine
Section titled “How policies combine”- Same column, several policies: OR. Policies for
region = USandregion = EUmean the user sees US or EU rows. - Different columns: AND. Policies for
region = USanddepartment = Salesmean only rows that match both. - No policies: every row, except in sources marked access-controlled, which show no rows until a policy applies.
User management
Section titled “User management”// Create a user$user = $querri->users->create([ 'email' => 'alice@example.com', 'external_id' => 'cust-42', 'first_name' => 'Alice', 'last_name' => 'Smith', 'role' => 'member', // member or admin]);
// Get or create by external ID$user = $querri->users->getOrCreate('cust-42', [ 'email' => 'alice@example.com', 'first_name' => 'Alice',]);
// Retrieve by Querri user ID$user = $querri->users->retrieve($userId);
// List users$page = $querri->users->list(['limit' => 50]);
// Filter by external ID$page = $querri->users->list(['external_id' => 'cust-42']);
// Update a user$querri->users->update($userId, ['role' => 'admin']);
// Delete a user$querri->users->del($userId);
// Remove an external ID mapping without deleting the user$querri->users->removeExternalId('cust-42');Embed sessions
Section titled “Embed sessions”For lower-level control than getSession():
// Create a session directly$session = $querri->embed->createSession([ 'user_id' => $userId, 'origin' => 'https://myapp.com', 'ttl' => 7200,]);
// Swap for a new token (the old one is revoked)$refreshed = $querri->embed->refreshSession($session['session_token']);
// List active sessions$sessions = $querri->embed->listSessions(['limit' => 50]);
// Revoke a session: pass its session token$querri->embed->revokeSession($session['session_token']);
// Revoke every session for a user (returns how many were revoked)$count = $querri->embed->revokeUserSessions($userId);User-scoped client
Section titled “User-scoped client”After creating a session, you can get a client that acts as that user. It only returns what the user can access:
$session = $querri->getSession(['user' => 'ext-123']);$userClient = $querri->asUser($session);
$projects = $userClient->projects->list();$dashboards = $userClient->dashboards->list();$sources = $userClient->sources->list();The user-scoped client has projects, dashboards, sources, data and chats. Its dashboards are read-only: list(), retrieve() and refreshStatus().
$querri->data reads and writes data sources:
// Create a source from rows$source = $querri->data->create([ 'name' => 'Sales Data', 'rows' => [['region' => 'US', 'revenue' => 1000]],]);
// Run SQL (the source is a view named data)$result = $querri->data->query($source['id'], [ 'sql' => 'SELECT region, SUM(revenue) AS total FROM data GROUP BY region', 'page' => 1, 'page_size' => 100,]);
// Read rows, append, replace$rows = $querri->data->getSourceData($source['id'], ['page' => 1, 'page_size' => 100]);$querri->data->appendRows($source['id'], ['rows' => [['region' => 'EU', 'revenue' => 800]]]);$querri->data->replaceData($source['id'], ['rows' => [['region' => 'US', 'revenue' => 1200]]]);Sharing
Section titled “Sharing”use Querri\Embed\Resources\SharingPermission;
// Share a project with a user$querri->sharing->shareProject($projectId, [ 'user_id' => $userId, 'permission' => SharingPermission::VIEW, // or SharingPermission::EDIT]);
// Share a dashboard$querri->sharing->shareDashboard($dashboardId, [ 'user_id' => $userId, 'permission' => SharingPermission::VIEW,]);
// Share a source with one user$querri->sharing->shareSource($sourceId, [ 'user_id' => $userId, 'permission' => SharingPermission::VIEW,]);
// Share a source with the whole organization$querri->sharing->orgShareSource($sourceId, ['enabled' => true, 'permission' => SharingPermission::VIEW]);
// List and revoke shares$querri->sharing->listProjectShares($projectId);$querri->sharing->revokeProjectShare($projectId, $userId);$querri->sharing->listDashboardShares($dashboardId);$querri->sharing->revokeDashboardShare($dashboardId, $userId);Error handling
Section titled “Error handling”The SDK throws typed exceptions:
use Querri\Embed\Exceptions\AuthenticationException;use Querri\Embed\Exceptions\NotFoundException;use Querri\Embed\Exceptions\PermissionException;use Querri\Embed\Exceptions\RateLimitException;use Querri\Embed\Exceptions\ValidationException;use Querri\Embed\Exceptions\ServerException;use Querri\Embed\Exceptions\QuerriException;
try { $session = $querri->getSession(['user' => 'ext-123']);} catch (AuthenticationException $e) { // 401: bad or expired API key} catch (PermissionException $e) { // 403: missing scope, blocked origin, or no access to the item} catch (RateLimitException $e) { // 429: back off and retry} catch (NotFoundException $e) { // 404: not found} catch (ValidationException $e) { // 400 or 422: bad parameters} catch (ServerException $e) { // 5xx: server error} catch (QuerriException $e) { // anything else from the SDK}Exception hierarchy
Section titled “Exception hierarchy”QuerriException├── ConfigException missing or invalid configuration├── ConnectionException network failure│ └── TimeoutException request timed out└── ApiException HTTP error response ├── ValidationException 400, 422 ├── AuthenticationException 401 ├── PermissionException 403 ├── NotFoundException 404 ├── ConflictException 409 ├── RateLimitException 429 └── ServerException 5xxRetries
Section titled “Retries”The SDK retries up to max_retries times (default 3), waiting longer each time and honoring Retry-After:
429is always retried.500,502,503and504, network failures and timeouts are retried only for requests that are safe to repeat:GET,PUT,DELETE,HEADandOPTIONS. APOSTorPATCHthat fails that way throws straight away.
Complete example: multi-tenant embed with row-level security
Section titled “Complete example: multi-tenant embed with row-level security”A Laravel route that creates a session for each tenant’s user:
use Querri\Embed\QuerriClient;
// Create once, for example in a service provider$querri = new QuerriClient([ 'api_key' => config('services.querri.api_key'), 'org_id' => config('services.querri.org_id'), 'default_origin' => config('app.url'),]);
// In your controllerRoute::get('/api/querri-token', function (Request $request) use ($querri) { $user = $request->user();
$session = $querri->getSession([ 'user' => [ 'external_id' => (string) $user->id, 'email' => $user->email, 'first_name' => $user->first_name, 'last_name' => $user->last_name, ], 'access' => [ 'sources' => [config('services.querri.source_id')], 'filters' => ['tenant_id' => (string) $user->tenant_id], ], 'origin' => $request->headers->get('Origin') ?: null, ]);
return response()->json($session);});Each tenant sees only their own rows: one dataset, one embed, filtered per user.
Security best practices
Section titled “Security best practices”- Keep API keys (
qk_) on the server. Never put them in client code or version control. - Session tokens (
es_) are safe in the browser. They’re tied to one user and expire. - Pass
origin, or setdefault_origin, so each session is bound to your site. - Store credentials in environment variables (
QUERRI_API_KEY,QUERRI_ORG_ID). - Replace API keys before they expire, from Settings → API Keys.
Upgrading to 1.0.0
Section titled “Upgrading to 1.0.0”1.0.0 matches the current API. The main changes from 0.2.x:
org_idis required when you create the client.$querri->datauses/sources, andquery()takes the source ID first:query($sourceId, ['sql' => ...]).$querri->sources->create()takesnameandrows. There’s no connector-based create.- The user-scoped client’s dashboards are read-only.
- New:
default_originandQUERRI_EMBED_ORIGIN, client-side checks onttlandorigin, and422mapped toValidationException.
API reference
Section titled “API reference”The PHP SDK wraps the Querri public API. For every endpoint, see the API Reference.
| SDK resource | API endpoints (under /api/v1) |
|---|---|
$querri->users | /users, /users/external/{external_id} |
$querri->embed | /embed/sessions |
$querri->policies | /access/policies, /access/resolve, /access/columns, /access/users/{user_id}/policies |
$querri->projects | /projects |
$querri->chats | /projects/{project_id}/chats |
$querri->dashboards | /dashboards |
$querri->data | /sources, /sources/{source_id}/data, /sources/{source_id}/query, /sources/{source_id}/rows |
$querri->sources | /sources, /connectors |
$querri->files | /files |
$querri->sharing | /projects/{project_id}/shares, /dashboards/{dashboard_id}/shares, /sources/{source_id}/shares, /sources/{source_id}/org-share |
$querri->keys | /keys |
$querri->audit | /audit/events |
$querri->usage | /usage |