QlikElixir

Hex.pmDocsLicense

A comprehensive Elixir client for Qlik Cloud REST APIs and QIX Engine.

Features

REST APIs - Full coverage of Qlik Cloud management APIs:

QIX Engine - Real-time data extraction via WebSocket:

Installation

Add qlik_elixir to your dependencies in mix.exs:

def deps do
[
{:qlik_elixir, "~> 0.3.3"}
]
end

Quick Start

Configuration

# Option 1: Environment variables (recommended for production)
# QLIK_API_KEY=your-api-key
# QLIK_TENANT_URL=https://your-tenant.region.qlikcloud.com
# Option 2: Application config
config :qlik_elixir,
api_key: "your-api-key",
tenant_url: "https://your-tenant.region.qlikcloud.com"
# Option 3: Runtime config (for multiple tenants)
config = QlikElixir.new_config(
api_key: "your-api-key",
tenant_url: "https://your-tenant.region.qlikcloud.com"
)

REST API Examples

# List apps
{:ok, %{"data" => apps}} = QlikElixir.REST.Apps.list()
# Get app details
{:ok, app} = QlikElixir.REST.Apps.get("app-id")
# Trigger a reload
{:ok, reload} = QlikElixir.REST.Reloads.create("app-id")
# Upload a CSV file
{:ok, file} = QlikElixir.REST.DataFiles.upload_file("sales_data.csv")
# List spaces
{:ok, %{"data" => spaces}} = QlikElixir.REST.Spaces.list()

QIX Engine - Data Extraction

alias QlikElixir.QIX.{Session, App}
# Connect to an app
{:ok, session} = Session.connect("app-id", config: config)
# List sheets
{:ok, sheets} = App.list_sheets(session)
# Get visualization data (the main event!)
{:ok, data} = App.get_hypercube_data(session, "object-id")
# Returns:
# %{
# headers: ["Country", "Sales", "Margin"],
# rows: [
# %{text: ["USA", "$1.2M", "23%"], values: ["USA", 1200000, 0.23]},
# %{text: ["Germany", "$900K", "19%"], values: ["Germany", 900000, 0.19]}
# ],
# total_rows: 50,
# truncated: false
# }
# Make selections
:ok = App.select_values(session, "Country", ["USA", "Germany"])
# Evaluate expressions
{:ok, total} = App.evaluate(session, "Sum(Sales)")
# Disconnect
:ok = Session.disconnect(session)

API Reference

REST APIs

ModuleDescriptionQlik API Reference
QlikElixir.REST.AppsApp management, publishing, export/importApps API
QlikElixir.REST.SpacesSpaces and role assignmentsSpaces API
QlikElixir.REST.DataFilesFile upload and managementData Files API
QlikElixir.REST.ReloadsTrigger and monitor reloadsReloads API
QlikElixir.REST.UsersUser managementUsers API
QlikElixir.REST.GroupsGroup managementGroups API
QlikElixir.REST.APIKeysAPI key managementAPI Keys API
QlikElixir.REST.AutomationsAutomation workflowsAutomations API
QlikElixir.REST.WebhooksEvent notificationsWebhooks API
QlikElixir.REST.DataConnectionsExternal data sourcesData Connections API
QlikElixir.REST.ItemsUnified resource listingItems API
QlikElixir.REST.CollectionsContent organizationCollections API
QlikElixir.REST.ReportsReport generationReports API
QlikElixir.REST.TenantsTenant configurationTenants API
QlikElixir.REST.RolesRole definitionsRoles API
QlikElixir.REST.AuditsAudit loggingAudits API
QlikElixir.REST.NaturalLanguageConversational analyticsInsight Advisor API

QIX Engine (WebSocket)

ModuleDescriptionQlik API Reference
QlikElixir.QIX.SessionWebSocket connection managementQIX Overview
QlikElixir.QIX.AppHigh-level data extraction APIDoc API
QlikElixir.QIX.ProtocolJSON-RPC protocol handlingGenericObject API

Core Modules

ModuleDescription
QlikElixir.ConfigConfiguration management
QlikElixir.ErrorError types and handling
QlikElixir.PaginationCursor-based pagination helpers

API Testing Status

All 362 tests pass (100% coverage with Bypass HTTP mocking).

The following table shows integration testing status against real Qlik Cloud APIs:

ModuleReadWriteNotes
Appscreate, get, update, copy, delete, get_metadata, get_lineage, get_script, validate_script, list_media, get_thumbnail, export
Spacescreate, get, update, delete, list_types, list_assignments
DataFileslist, get, upload, delete, find_by_name
Reloadslist, get, create, cancel
Collectionscreate, get, update, delete, list_items, add_item, remove_item, get_favorites
Items-list, get, find_by_resource, get_published_items, get_collections
Users-me, list, count
Groups-list, list_settings
Roles-list, get
APIKeys-get_config requires tenant_id
Automations-list, list_runs
Webhooks-list, list_event_types
DataConnections-list, get
NaturalLanguage-get_model, list_analysis_types, ask, recommend
Audits-list, get, list_sources, list_types
Tenants-me
Reports⚠️-API returns 404 (may require entitlement)

Untested Write Operations

The following write operations have unit tests but have not been integration tested:

ModuleUntested OperationsReason
Appspublish, import_appRequires published app setup
Spacescreate_assignment, delete_assignmentRequires user IDs
DataFilesupdate, change_owner, change_space, batch_*Requires specific setup
Itemsupdate, deleteAffects catalog items
Userscreate, update, delete, inviteTenant admin operations
Groupscreate, update, delete, update_settingsGroup management
APIKeyscreate, update, delete, update_configSecurity sensitive
Automationscreate, update, delete, run, enable, disable, etc.Complex setup
Webhookscreate, update, delete, resend_deliveryRequires callback URL
DataConnectionscreate, update, deleteRequires datasourceID
Tenantsget, create, update, deactivate, reactivateTenant admin only

QIX Engine (WebSocket): ✅ Fully integration tested - Session, App, data extraction

Common Patterns

Pagination

All list endpoints support cursor-based pagination:

# First page
{:ok, %{"data" => apps, "links" => %{"next" => %{"href" => next_url}}}} =
QlikElixir.REST.Apps.list(limit: 20)
# Get cursor from next URL and fetch next page
{:ok, page2} = QlikElixir.REST.Apps.list(limit: 20, next: cursor)
# Or use the Pagination helper
QlikElixir.Pagination.stream(fn cursor ->
QlikElixir.REST.Apps.list(limit: 100, next: cursor)
end)
|> Enum.take(500) # Get up to 500 apps

Error Handling

case QlikElixir.REST.Apps.get("app-id") do
{:ok, app} ->
IO.puts("Found app: #{app["name"]}")
{:error, %QlikElixir.Error{type: :not_found}} ->
IO.puts("App not found")
{:error, %QlikElixir.Error{type: :authentication_error}} ->
IO.puts("Invalid API key")
{:error, %QlikElixir.Error{} = error} ->
IO.puts("Error: #{error.message}")
end

Multiple Tenants

# Create configs for different tenants
us_config = QlikElixir.new_config(
api_key: System.fetch_env!("US_QLIK_API_KEY"),
tenant_url: "https://us-tenant.us.qlikcloud.com"
)
eu_config = QlikElixir.new_config(
api_key: System.fetch_env!("EU_QLIK_API_KEY"),
tenant_url: "https://eu-tenant.eu.qlikcloud.com"
)
# Use specific config per request
{:ok, us_apps} = QlikElixir.REST.Apps.list(config: us_config)
{:ok, eu_apps} = QlikElixir.REST.Apps.list(config: eu_config)

Streaming Large Datasets

alias QlikElixir.QIX.{Session, App}
{:ok, session} = Session.connect("app-id", config: config)
# Stream hypercube data in pages
App.stream_hypercube_data(session, "object-id", page_size: 1000)
|> Stream.flat_map(& &1)
|> Stream.each(fn row ->
# Process each row
IO.inspect(row)
end)
|> Stream.run()

Configuration Options

config = QlikElixir.new_config(
# Required
api_key: "your-api-key",
tenant_url: "https://your-tenant.region.qlikcloud.com",
# Optional
connection_id: "default-connection-id", # For data files
http_options: [
timeout: :timer.minutes(5), # Request timeout
retry: :transient, # Retry strategy
max_retries: 3, # Max retry attempts
retry_delay: fn n -> n * 1000 end # Backoff function
]
)

Development

# Install dependencies
mix deps.get
# Run tests
mix test
# Run with coverage
mix test --cover
# Check code quality
mix format --check-formatted
mix credo --strict
mix dialyzer
# Generate docs
mix docs

Roadmap / TODO

PRs welcome! Here are some areas that could use contribution:

Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Write tests first (TDD encouraged)
  4. Ensure all checks pass (mix format && mix credo --strict && mix test)
  5. Commit your changes
  6. Push to the branch
  7. Open a Pull Request

License

MIT License - see LICENSE for details.


Sponsored by

This project is proudly sponsored by Balneario - Clínica de Longevidad de Cofrentes, a world-class longevity clinic and thermal spa in Valencia, Spain. Their support makes open source development like this possible.

Thank you for investing in the developer community!