Authorization

Secure API access using API Keys, OAuth 2.0, or plugin authorization

Overview

The system supports three authorization methods to authenticate API requests and access resources securely. Choose the method that best fits your integration requirements and security needs.

Authorization Methods

API Key

Simplest method for internal integrations and scripts. Static, long-lived credentials.

Recommended for: Internal tools

OAuth 2.0

Industry-standard protocol for third-party applications with user consent and token management.

Recommended for: External apps

Plugin Authorization

Automatic credentials provided to plugins. No manual configuration required.

Recommended for: Plugins


API Key Authorization

The simplest authorization method using a static API key for authentication.

How It Works

API Keys are long-lived, static credentials that identify your application. They’re stored directly in the database (encrypted) and are perfect for server-to-server integrations, internal tools, and scripts where you control the environment.

**Security Note:** API Keys should be kept secret and never exposed in client-side code or public repositories. They provide full access to your account.

Creating an API Key

  1. Navigate to API Keys page
  2. Click “Create New API Key”
  3. Set a descriptive name and optional expiration date
  4. Copy the generated key immediately (it won’t be shown again)
  5. Store the key securely in your application configuration

Usage Examples

TypeScript - Using Query API

const API_KEY = 'your_api_key_here';
const API_URL = 'https://your-domain.com';

async function fetchProducts() {
  try {
    const response = await fetch(
      `${API_URL}/api/query`,
      {
        method: 'POST',
        headers: {
          'X-API-Key': API_KEY,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          query: `products { 
            id, 
            name, 
            price, 
            stock 
          }`
        })
      }
    );
    
    if (!response.ok) {
      throw new Error(`API Error: ${response.status}`);
    }
    
    const data = await response.json();
    return data.products;
  } catch (error) {
    console.error('API Error:', error);
    throw error;
  }
}

// Usage
fetchProducts().then(products => {
  console.log(`Found ${products.length} products`);
  products.forEach(p => {
    console.log(`${p.name}: ${p.price}`);
  });
});

TypeScript - Using OData API

const API_KEY = 'your_api_key_here';
const API_URL = 'https://your-domain.com';

async function fetchProductsOData() {
  try {
    const params = new URLSearchParams({
      '$select': 'id,name,price,stock',
      '$filter': 'stock gt 0',
      '$top': '100',
      '$orderby': 'name asc'
    });
    
    const response = await fetch(
      `${API_URL}/odata/products?${params}`,
      {
        headers: {
          'X-API-Key': API_KEY
        }
      }
    );
    
    if (!response.ok) {
      throw new Error(`OData Error: ${response.status}`);
    }
    
    const data = await response.json();
    return data.value;
  } catch (error) {
    console.error('OData Error:', error);
    throw error;
  }
}

// Usage
fetchProductsOData().then(products => {
  console.log('In-stock products:', products);
});

TypeScript - Using Import API

import fs from 'fs';

const API_KEY = 'your_api_key_here';
const API_URL = 'https://your-domain.com';

async function importProducts(filePath: string) {
  const formData = new FormData();
  const fileContent = fs.readFileSync(filePath);
  const blob = new Blob([fileContent]);
  
  formData.append('file', blob, 'products.csv');
  formData.append('entity', 'products');
  formData.append('mapperId', 'your-mapper-id');

  try {
    const response = await fetch(
      `${API_URL}/api/import`,
      {
        method: 'POST',
        headers: {
          'X-API-Key': API_KEY
        },
        body: formData
      }
    );
    
    if (!response.ok) {
      throw new Error(`Import Error: ${response.status}`);
    }
    
    return await response.json();
  } catch (error) {
    console.error('Import Error:', error);
    throw error;
  }
}

// Usage
importProducts('./products.csv').then(result => {
  console.log(`Import completed: ${result.imported} imported`);
});

PHP - Using Query API

<?php

$apiKey = 'your_api_key_here';
$apiUrl = 'https://your-domain.com';

function fetchProducts($apiKey, $apiUrl) {
    $query = [
        'query' => 'products { id, name, price, stock }'
    ];
    
    $ch = curl_init($apiUrl . '/api/query');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($query));
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'X-API-Key: ' . $apiKey,
        'Content-Type: application/json'
    ]);
    
    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    
    if ($httpCode !== 200) {
        throw new Exception('API Error: ' . $httpCode);
    }
    
    $data = json_decode($response, true);
    return $data['products'];
}

// Usage
try {
    $products = fetchProducts($apiKey, $apiUrl);
    echo "Found " . count($products) . " products\n";
    
    foreach ($products as $product) {
        echo "{$product['name']}: {$product['price']}\n";
    }
} catch (Exception $e) {
    echo "Error: " . $e->getMessage() . "\n";
}

PHP - Using OData API

<?php

$apiKey = 'your_api_key_here';
$apiUrl = 'https://your-domain.com';

function fetchProductsOData($apiKey, $apiUrl) {
    $params = [
        '$select' => 'id,name,price,stock',
        '$filter' => 'stock gt 0',
        '$top' => 100,
        '$orderby' => 'name asc'
    ];
    
    $url = $apiUrl . '/odata/products?' . http_build_query($params);
    
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'X-API-Key: ' . $apiKey
    ]);
    
    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    
    if ($httpCode !== 200) {
        throw new Exception('OData Error: ' . $httpCode);
    }
    
    $data = json_decode($response, true);
    return $data['value'];
}

// Usage
try {
    $products = fetchProductsOData($apiKey, $apiUrl);
    echo "In-stock products: " . count($products) . "\n";
    print_r($products);
} catch (Exception $e) {
    echo "Error: " . $e->getMessage() . "\n";
}

PHP - Using Import API

<?php

$apiKey = 'your_api_key_here';
$apiUrl = 'https://your-domain.com';

function importProducts($apiKey, $apiUrl, $filePath) {
    $file = new CURLFile($filePath, 'text/csv', 'products.csv');
    
    $postData = [
        'file' => $file,
        'entity' => 'products',
        'mapperId' => 'your-mapper-id'
    ];
    
    $ch = curl_init($apiUrl . '/api/import');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'X-API-Key: ' . $apiKey
    ]);
    
    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    
    if ($httpCode !== 200) {
        throw new Exception('Import Error: ' . $httpCode);
    }
    
    return json_decode($response, true);
}

// Usage
try {
    $result = importProducts($apiKey, $apiUrl, './products.csv');
    echo "Import completed: " . $result['imported'] . " imported\n";
} catch (Exception $e) {
    echo "Error: " . $e->getMessage() . "\n";
}

Best Practices

Store securely: Use environment variables or secure configuration files

Set expiration: Configure automatic expiration dates for added security

Rotate regularly: Create new keys and deactivate old ones periodically

Use HTTPS: Always use HTTPS to prevent key interception

Never commit: Don’t commit API keys to version control systems


OAuth 2.0 Authorization

Industry-standard protocol for secure delegated access with user consent.

How It Works

OAuth 2.0 is the industry-standard protocol for authorization. It enables third-party applications to obtain limited access to user accounts without exposing passwords. The authorization flow uses short-lived access tokens and long-lived refresh tokens, with secrets stored encrypted in SafeStore using split-knowledge encryption (AES-256-CBC).

**Security:** 
OAuth tokens are more secure than API keys. Access tokens are JWT tokens that expire, and secrets are encrypted with both server-side pepper and user-specific encryption keys.

Setup Steps

1. Create a Developer Account

Register as a developer in the system to access OAuth features

2. Create a Developer Application

Create an application with redirect URIs, scopes, and OAuth settings

3. Get Client Credentials

Copy your Client ID and Client Secret from the application settings

4. Implement OAuth Flow

Use the Authorization Code Flow to obtain access tokens for your users

Complete OAuth 2.0 Documentation

For detailed information, see:

OAuth Flow Examples

TypeScript - Step 1: Redirect User to Authorization

import crypto from 'crypto';

const CLIENT_ID = 'your_client_id';
const REDIRECT_URI = 'https://yourapp.com/oauth/callback';
const API_URL = 'https://your-domain.com';

// Generate state parameter for CSRF protection
const state = crypto.randomBytes(16).toString('hex');
sessionStorage.setItem('oauth_state', state);

// Build authorization URL
const authUrl = new URL(`${API_URL}/auth/oauth/authorize`);
authUrl.searchParams.set('client_id', CLIENT_ID);
authUrl.searchParams.set('redirect_uri', REDIRECT_URI);
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('scope', 'read write');
authUrl.searchParams.set('state', state);

// Redirect to authorization page
window.location.href = authUrl.toString();

TypeScript - Step 2: Handle Callback and Exchange Code

const CLIENT_ID = 'your_client_id';
const CLIENT_SECRET = 'your_client_secret';
const REDIRECT_URI = 'https://yourapp.com/oauth/callback';
const API_URL = 'https://your-domain.com';

interface TokenResponse {
  access_token: string;
  token_type: string;
  expires_in: number;
  refresh_token?: string;
  scope: string;
}

async function handleOAuthCallback(code: string, state: string): Promise<TokenResponse> {
  // Verify state parameter
  const savedState = sessionStorage.getItem('oauth_state');
  if (state !== savedState) {
    throw new Error('Invalid state parameter - CSRF attack detected');
  }

  // Exchange authorization code for tokens
  try {
    const response = await fetch(
      `${API_URL}/auth/oauth/token`,
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded'
        },
        body: new URLSearchParams({
          grant_type: 'authorization_code',
          code: code,
          redirect_uri: REDIRECT_URI,
          client_id: CLIENT_ID,
          client_secret: CLIENT_SECRET
        })
      }
    );

    if (!response.ok) {
      throw new Error(`Token exchange failed: ${response.status}`);
    }

    const tokens: TokenResponse = await response.json();

    // Store tokens securely
    localStorage.setItem('access_token', tokens.access_token);
    if (tokens.refresh_token) {
      localStorage.setItem('refresh_token', tokens.refresh_token);
    }

    return tokens;
  } catch (error) {
    console.error('Token exchange failed:', error);
    throw error;
  }
}

// Usage in callback route
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
const state = urlParams.get('state');

if (code && state) {
  handleOAuthCallback(code, state).then(tokens => {
    console.log('OAuth successful, access token obtained');
    // Now you can make API calls
  });
}

TypeScript - Step 3: Making Authenticated Requests

const API_URL = 'https://your-domain.com';
const CLIENT_ID = 'your_client_id';
const CLIENT_SECRET = 'your_client_secret';

async function makeAuthenticatedRequest(endpoint: string, data?: any) {
  const accessToken = localStorage.getItem('access_token');
  
  if (!accessToken) {
    throw new Error('No access token available');
  }

  try {
    const response = await fetch(
      `${API_URL}${endpoint}`,
      {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${accessToken}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(data)
      }
    );

    if (response.status === 401) {
      // Token expired, refresh it
      await refreshAccessToken();
      // Retry request
      return makeAuthenticatedRequest(endpoint, data);
    }

    if (!response.ok) {
      throw new Error(`Request failed: ${response.status}`);
    }

    return await response.json();
  } catch (error) {
    throw error;
  }
}

// Refresh token when access token expires
async function refreshAccessToken() {
  const refreshToken = localStorage.getItem('refresh_token');
  
  if (!refreshToken) {
    throw new Error('No refresh token available');
  }

  const response = await fetch(
    `${API_URL}/auth/oauth/token`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
      },
      body: new URLSearchParams({
        grant_type: 'refresh_token',
        refresh_token: refreshToken,
        client_id: CLIENT_ID,
        client_secret: CLIENT_SECRET
      })
    }
  );

  if (!response.ok) {
    throw new Error(`Token refresh failed: ${response.status}`);
  }

  const tokens = await response.json();
  localStorage.setItem('access_token', tokens.access_token);
  return tokens;
}

// Usage
makeAuthenticatedRequest('/api/query', {
  query: 'products { id, name, price }'
}).then(data => {
  console.log('Products:', data.products);
});

PHP - Step 1: Redirect User to Authorization

<?php
session_start();

$clientId = 'your_client_id';
$redirectUri = 'https://yourapp.com/oauth/callback.php';
$apiUrl = 'https://your-domain.com';

// Generate state parameter for CSRF protection
$state = bin2hex(random_bytes(16));
$_SESSION['oauth_state'] = $state;

// Build authorization URL
$params = [
    'client_id' => $clientId,
    'redirect_uri' => $redirectUri,
    'response_type' => 'code',
    'scope' => 'read write',
    'state' => $state
];

$authUrl = $apiUrl . '/auth/oauth/authorize?' . http_build_query($params);

// Redirect to authorization page
header('Location: ' . $authUrl);
exit;

PHP - Step 2: Handle Callback and Exchange Code

<?php
session_start();

$clientId = 'your_client_id';
$clientSecret = 'your_client_secret';
$redirectUri = 'https://yourapp.com/oauth/callback.php';
$apiUrl = 'https://your-domain.com';

function exchangeCodeForToken($code, $state) {
    global $clientId, $clientSecret, $redirectUri, $apiUrl;
    
    // Verify state parameter
    if (!isset($_SESSION['oauth_state']) || $state !== $_SESSION['oauth_state']) {
        throw new Exception('Invalid state parameter - CSRF attack detected');
    }
    
    unset($_SESSION['oauth_state']);
    
    // Prepare token request
    $postData = [
        'grant_type' => 'authorization_code',
        'code' => $code,
        'redirect_uri' => $redirectUri,
        'client_id' => $clientId,
        'client_secret' => $clientSecret
    ];
    
    // Exchange code for tokens
    $ch = curl_init($apiUrl . '/auth/oauth/token');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Content-Type: application/x-www-form-urlencoded'
    ]);
    
    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    
    if ($httpCode !== 200) {
        throw new Exception('Token exchange failed: ' . $httpCode);
    }
    
    $tokens = json_decode($response, true);
    
    // Store tokens securely (in session, database, or secure cookie)
    $_SESSION['access_token'] = $tokens['access_token'];
    if (isset($tokens['refresh_token'])) {
        $_SESSION['refresh_token'] = $tokens['refresh_token'];
    }
    $_SESSION['token_expires'] = time() + $tokens['expires_in'];
    
    return $tokens;
}

// Handle callback
if (isset($_GET['code']) && isset($_GET['state'])) {
    try {
        $tokens = exchangeCodeForToken($_GET['code'], $_GET['state']);
        echo "OAuth successful! Access token obtained.\n";
        echo "Token expires in: " . $tokens['expires_in'] . " seconds\n";
        
        // Redirect to your application
        header('Location: /dashboard.php');
        exit;
    } catch (Exception $e) {
        echo "OAuth Error: " . $e->getMessage();
    }
} else if (isset($_GET['error'])) {
    echo "OAuth Error: " . $_GET['error'];
}

PHP - Step 3: Making Authenticated Requests

<?php
session_start();

$clientId = 'your_client_id';
$clientSecret = 'your_client_secret';
$apiUrl = 'https://your-domain.com';

function makeAuthenticatedRequest($endpoint, $data = null) {
    global $apiUrl;
    
    if (!isset($_SESSION['access_token'])) {
        throw new Exception('No access token available');
    }
    
    // Check if token is expired
    if (isset($_SESSION['token_expires']) && time() >= $_SESSION['token_expires']) {
        refreshAccessToken();
    }
    
    $ch = curl_init($apiUrl . $endpoint);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    
    if ($data !== null) {
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
    }
    
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Authorization: Bearer ' . $_SESSION['access_token'],
        'Content-Type: application/json'
    ]);
    
    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    
    if ($httpCode === 401) {
        // Token expired, refresh and retry
        refreshAccessToken();
        return makeAuthenticatedRequest($endpoint, $data);
    }
    
    if ($httpCode !== 200) {
        throw new Exception('API Error: ' . $httpCode);
    }
    
    return json_decode($response, true);
}

function refreshAccessToken() {
    global $apiUrl, $clientId, $clientSecret;
    
    if (!isset($_SESSION['refresh_token'])) {
        throw new Exception('No refresh token available');
    }
    
    $postData = [
        'grant_type' => 'refresh_token',
        'refresh_token' => $_SESSION['refresh_token'],
        'client_id' => $clientId,
        'client_secret' => $clientSecret
    ];
    
    $ch = curl_init($apiUrl . '/auth/oauth/token');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Content-Type: application/x-www-form-urlencoded'
    ]);
    
    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    
    if ($httpCode !== 200) {
        throw new Exception('Token refresh failed: ' . $httpCode);
    }
    
    $tokens = json_decode($response, true);
    $_SESSION['access_token'] = $tokens['access_token'];
    $_SESSION['token_expires'] = time() + $tokens['expires_in'];
}

// Usage
try {
    $data = makeAuthenticatedRequest('/api/query', [
        'query' => 'products { id, name, price }'
    ]);
    
    echo "Found " . count($data['products']) . " products\n";
    foreach ($data['products'] as $product) {
        echo $product['name'] . ": " . $product['price'] . "\n";
    }
} catch (Exception $e) {
    echo "Error: " . $e->getMessage();
}

Key Features

User consent: Users explicitly authorize your application

JWT tokens: Access tokens are stateless JWT tokens (30-day expiration)

Encrypted storage: Secrets stored with AES-256-CBC split-knowledge encryption

Refresh tokens: Long-lived tokens for obtaining new access tokens

CSRF protection: State parameter prevents cross-site request forgery


Plugin Authorization

Automatic OAuth credentials for plugins with zero configuration.

How It Works

When developing plugins for the system, you don’t need to manually create developer applications or manage OAuth credentials. The system automatically provides your plugin with the necessary client_id and client_secret at runtime through environment variables or configuration.

**Automatic Setup:** 
Plugin authorization is handled automatically by the system. Your plugin code simply reads the provided credentials from environment variables.

How Plugins Receive Credentials

When your plugin is loaded, the system provides OAuth credentials through:

OAUTH_CLIENT_ID Your plugin’s unique client identifier

OAUTH_CLIENT_SECRET Your plugin’s secret key for authentication

API_BASE_URL The base URL for making API requests

Plugin Example

TypeScript Plugin

// Plugin receives these credentials automatically
// OAUTH_CLIENT_ID will be provided by the system
// OAUTH_CLIENT_SECRET will be provided by the system
// API_BASE_URL will be provided by the system

const CLIENT_ID = process.env.OAUTH_CLIENT_ID!;
const CLIENT_SECRET = process.env.OAUTH_CLIENT_SECRET!;
const API_BASE_URL = process.env.API_BASE_URL!;

class PluginApiClient {
  private accessToken: string | null = null;
  private tokenExpiry: number = 0;

  async authenticate() {
    // Use client credentials grant
    const response = await fetch(
      `${API_BASE_URL}/auth/oauth/token`,
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded'
        },
        body: new URLSearchParams({
          grant_type: 'client_credentials',
          client_id: CLIENT_ID,
          client_secret: CLIENT_SECRET,
          scope: 'read write'
        })
      }
    );

    if (!response.ok) {
      throw new Error(`Authentication failed: ${response.status}`);
    }

    const tokens = await response.json();
    this.accessToken = tokens.access_token;
    this.tokenExpiry = Date.now() + (tokens.expires_in * 1000);
  }

  async makeRequest(endpoint: string, data?: any) {
    // Refresh token if expired
    if (!this.accessToken || Date.now() >= this.tokenExpiry) {
      await this.authenticate();
    }

    const response = await fetch(
      `${API_BASE_URL}${endpoint}`,
      {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${this.accessToken}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(data)
      }
    );

    if (!response.ok) {
      throw new Error(`Request failed: ${response.status}`);
    }

    return await response.json();
  }
}

// Usage in your plugin
const client = new PluginApiClient();

export async function pluginFunction() {
  const products = await client.makeRequest('/api/query', {
    query: 'products { id, name, price }'
  });

  console.log('Fetched products:', products);
  return products;
}

PHP Plugin

<?php

// Plugin receives these credentials automatically
// OAUTH_CLIENT_ID will be provided by the system
// OAUTH_CLIENT_SECRET will be provided by the system
// API_BASE_URL will be provided by the system

$clientId = getenv('OAUTH_CLIENT_ID');
$clientSecret = getenv('OAUTH_CLIENT_SECRET');
$apiBaseUrl = getenv('API_BASE_URL');

class PluginApiClient {
    private $clientId;
    private $clientSecret;
    private $apiBaseUrl;
    private $accessToken = null;
    private $tokenExpiry = 0;

    public function __construct($clientId, $clientSecret, $apiBaseUrl) {
        $this->clientId = $clientId;
        $this->clientSecret = $clientSecret;
        $this->apiBaseUrl = $apiBaseUrl;
    }

    private function authenticate() {
        $postData = [
            'grant_type' => 'client_credentials',
            'client_id' => $this->clientId,
            'client_secret' => $this->clientSecret,
            'scope' => 'read write'
        ];

        $ch = curl_init($this->apiBaseUrl . '/auth/oauth/token');
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Content-Type: application/x-www-form-urlencoded'
        ]);

        $response = curl_exec($ch);
        curl_close($ch);

        $data = json_decode($response, true);
        $this->accessToken = $data['access_token'];
        $this->tokenExpiry = time() + $data['expires_in'];
    }

    public function makeRequest($endpoint, $data = null) {
        // Refresh token if expired
        if ($this->accessToken === null || time() >= $this->tokenExpiry) {
            $this->authenticate();
        }

        $ch = curl_init($this->apiBaseUrl . $endpoint);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        
        if ($data !== null) {
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
        }
        
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Authorization: Bearer ' . $this->accessToken,
            'Content-Type: application/json'
        ]);

        $response = curl_exec($ch);
        curl_close($ch);

        return json_decode($response, true);
    }
}

// Usage in your plugin
$client = new PluginApiClient($clientId, $clientSecret, $apiBaseUrl);

function pluginFunction($client) {
    $products = $client->makeRequest('/api/query', [
        'query' => 'products { id, name, price }'
    ]);

    echo "Fetched products: " . count($products['products']) . "\n";
    return $products;
}

// Execute plugin function
pluginFunction($client);

Benefits of Plugin Authorization

Zero configuration: No manual setup required for developers

Automatic injection: Credentials provided via environment variables

Secure by default: Uses OAuth 2.0 with encrypted storage

Consistent API: Same authentication flow as external applications


Comparison Table

Choose the right authorization method for your use case.

FeatureAPI KeyOAuth 2.0Plugin Authorization
Setup ComplexitySimpleModerateAutomatic
Security LevelBasic (static secrets)High (encrypted, expires)High (OAuth-based)
Token ExpirationManual/configurable30 days (JWT)30 days (JWT)
User ConsentNoYes (explicit)No (system-level)
Best ForInternal tools, scriptsThird-party appsSystem plugins
Refresh CapabilityN/A (static)Yes (refresh tokens)Yes (automatic)
Configuration RequiredCreate API keyCreate developer + appNone (automatic)

API Endpoints

API Key Authentication

All API endpoints support API Key authentication via the X-API-Key header:

POST /api/query
X-API-Key: your_api_key_here
Content-Type: application/json
GET /odata/products
X-API-Key: your_api_key_here
POST /api/import
X-API-Key: your_api_key_here
Content-Type: multipart/form-data

OAuth 2.0 Endpoints

Authorization Endpoint

GET /auth/oauth/authorize
  ?client_id={client_id}
  &redirect_uri={redirect_uri}
  &response_type=code
  &scope={scopes}
  &state={state}

Token Endpoint

POST /auth/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code={authorization_code}
&redirect_uri={redirect_uri}
&client_id={client_id}
&client_secret={client_secret}

Token Refresh

POST /auth/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
&refresh_token={refresh_token}
&client_id={client_id}
&client_secret={client_secret}

Client Credentials Grant (for plugins)

POST /auth/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&client_id={client_id}
&client_secret={client_secret}
&scope={scopes}

Authenticated API Requests

Once you have an access token, include it in the Authorization header:

POST /api/query
Authorization: Bearer {access_token}
Content-Type: application/json

{
  "query": "products { id, name, price }"
}

[navigate:/help/en/api/overview|API Overview|book-open|left]

[navigate:/help/en/api/query|Query API|code|left]

[navigate:/help/en/api/odata|OData API|database|left]

[navigate:/help/en/api/imports|Import API|upload|left]

[navigate:/help/en/api/webhooks|Webhooks|webhook|left]

[navigate:/help/en/api/rate-limits|Rate Limits|gauge|left]