Ir al contenido

PHP SDK

Esta página aún no está disponible en español. Se muestra la versión en inglés.

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.

Terminal window
composer require querri/embed

Upgrading from 0.2.x? See Upgrading to 1.0.0.

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 frontend
echo json_encode(['sessionToken' => $session->sessionToken]);

Then render the embed on your frontend with the Embed SDK and that session token.

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 environment

Environment variables only

Terminal window
export QUERRI_API_KEY="qk_live_..."
export QUERRI_ORG_ID="your_org_id"
$querri = new QuerriClient(); // reads from the environment
OptionEnvironment variableDefaultDescription
api_keyQUERRI_API_KEY(required)Your qk_ API key
org_idQUERRI_ORG_ID(required)Organization ID
hostQUERRI_URLhttps://app.querri.comServer URL
default_originQUERRI_EMBED_ORIGINnoneOrigin used for embed sessions when you don’t pass one
timeout30.0Request timeout in seconds
max_retries3Retries for failed requests

Camel-case keys (apiKey, orgId, maxRetries, defaultOrigin) work too.

getSession() does three things in one call:

  1. Finds or creates the user by external_id.
  2. Applies access, creating or reusing an access policy when you pass inline filters.
  3. 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 expires

A 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.

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.

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.

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 them

See 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().

  • Same column, several policies: OR. Policies for region = US and region = EU mean the user sees US or EU rows.
  • Different columns: AND. Policies for region = US and department = Sales mean only rows that match both.
  • No policies: every row, except in sources marked access-controlled, which show no rows until a policy applies.
// 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');

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);

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]]]);
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);

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
}
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 5xx

The SDK retries up to max_retries times (default 3), waiting longer each time and honoring Retry-After:

  • 429 is always retried.
  • 500, 502, 503 and 504, network failures and timeouts are retried only for requests that are safe to repeat: GET, PUT, DELETE, HEAD and OPTIONS. A POST or PATCH that 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 controller
Route::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.

  • 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 set default_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.

1.0.0 matches the current API. The main changes from 0.2.x:

  • org_id is required when you create the client.
  • $querri->data uses /sources, and query() takes the source ID first: query($sourceId, ['sql' => ...]).
  • $querri->sources->create() takes name and rows. There’s no connector-based create.
  • The user-scoped client’s dashboards are read-only.
  • New: default_origin and QUERRI_EMBED_ORIGIN, client-side checks on ttl and origin, and 422 mapped to ValidationException.

The PHP SDK wraps the Querri public API. For every endpoint, see the API Reference.

SDK resourceAPI 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