GPLC Connector API reference
The v1 REST API the connector plugin uses: authenticate a site with a license key, browse the catalog, spend credits on installs, check for updates. Same contract, documented.
Overview
The v1 API provides endpoints for:
- Client authentication (WordPress site → Zeebrar)
- Plugin catalog access (all plugins visible to paying subscribers)
- Credit consumption for downloads
- Update checking
- Account status
https://zeebrar.com/api/v1
Authentication
License Key Auth (Preferred)
POST /api/v1/client/auth
Request:
{
"licenseKey": "zb_xxxxxxxxxxxxxxxx",
"siteUrl": "https://client-site.com",
"siteTitle": "Client Site"
}
Response:
{
"success": true,
"token": "eyJhbGciOiJIUzI1NiIs...",
"customer": {
"id": "cust_xxx",
"email": "[email protected]",
"name": "John Doe",
"tier": "agency",
"tierLabel": "Agency",
"subscription_status": "active"
},
"credits": {
"available": 89,
"used": 11,
"reset_at": "2026-02-01T00:00:00Z",
"_note_reset_at": "Next credit allocation date. null if subscription is cancelled (credits frozen)."
},
"subscriptions": [
{
"id": "sub_xxx",
"tier": "agency",
"status": "active",
"product_name": "Agency Monthly",
"start_date": "2026-01-01T00:00:00Z",
"end_date": null
}
],
"client": {
"id": "client_xxx",
"api_key": "zb_xxx"
},
"store": {
"api_url": "https://zeebrar.com",
"store_type": "saas_managed",
"brand_name": "Zeebrar",
"upgrade_url": "https://zeebrar.com/pricing",
"checkout_url": "https://zeebrar.com/checkout",
"account_url": "https://zeebrar.com/account"
}
}
Legacy Email/Password Auth (Deprecated)
POST /api/v1/client/auth
Request:
{
"email": "[email protected]",
"password": "xxx",
"site_url": "https://client-site.com",
"site_name": "Client Site"
}
Response includes _deprecation warning. Migrate to license key auth.
Subsequent Requests
All other endpoints require JWT Bearer token:
Authorization: Bearer <token>
Endpoints
Catalog
#### GET /api/v1/plugins
List all plugins in the catalog. All paying subscribers see the full catalog - credits are the only gate for downloads.
Query params:| Param | Type | Default | Description |
|---|---|---|---|
page | int | 1 | Page number |
limit | int | 20 | Items per page (max: 100) |
search | string | - | Search in name and description |
sort | string | featured | Sort order (see below) |
category | string | - | Filter by category slug |
tags | string | - | Comma-separated tag names (case-insensitive, plugins with at least one match) |
level | string | - | Exact product level: lite, pro, geek, exclusive |
new | string | - | If true, only show plugins added within the last 3 days |
| Value | Description |
|---|---|
featured | Featured plugins first, then alphabetical (default) |
name-asc | Alphabetical A-Z |
name-desc | Alphabetical Z-A |
updated | Most recently updated first |
new | Most recently added first (by date added to catalog) |
popular | Most downloaded first (Zeebrar store downloads only) |
relevance | Best search match first (auto-selected when search is provided) |
level: Filters by exact product level. Use when user wants to see only "Pro" plugins.
?level=pro → returns only plugins where tier_required == "pro"
- Example: ?level=lite → returns only Lite-level plugins
{
"plugins": [
{
"id": "uuid",
"slug": "plugin-name",
"name": "Plugin Name",
"version": "1.2.0",
"description": "Short description",
"credits_required": 1,
"tier_required": "pro",
"has_access": true,
"icon_url": "https://...",
"category": "forms",
"tags": ["woocommerce", "forms"],
"compatibility": {
"wp_requires": "6.0",
"wp_tested": "6.7",
"php_requires": "7.4",
"wc_requires": null,
"wc_tested": null
},
"updated_at": "2026-01-20T00:00:00Z",
"created_at": "2025-12-23T00:00:00Z"
}
],
"pagination": {
"page": 1,
"per_page": 20,
"total": 45,
"total_pages": 3
},
"filters": {
"categories": [
{ "name": "Forms", "slug": "forms", "count": 45 }
],
"tiers": [
{ "id": "lite", "credits": 1, "count": 100 },
{ "id": "pro", "credits": 3, "count": 50 }
]
}
}
#### GET /api/v1/plugins/[slug]
Get detailed plugin information including version history for rollback support.
Response:{
"plugin": {
"slug": "plugin-name",
"name": "Plugin Name",
"version": "1.2.0",
"currentVersion": "1.2.0",
"description": "Full description...",
"creditCost": 1,
"tier": "pro",
"tier_required": "pro",
"has_access": true,
"iconUrl": "https://...",
"changelog": "### 1.2.0\n- Feature A\n- Bug fix B",
"requires": "6.0",
"tested": "6.7",
"php_requires": "7.4",
"compatibility": {
"wp_requires": "6.0",
"wp_tested": "6.7",
"php_requires": "7.4",
"wc_requires": "8.0",
"wc_tested": "9.0"
},
"lastUpdated": "2026-01-20T00:00:00Z",
"versions": [
{
"version": "1.2.0",
"changelog": "### 1.2.0\n- Feature A",
"released_at": "2026-01-20T00:00:00Z",
"file_size": 245760,
"has_file": true,
"scan_status": "completed",
"threat_level": "clean",
"scan_report_url": "https://www.virustotal.com/gui/file/abc123..."
},
{
"version": "1.1.0",
"changelog": "### 1.1.0\n- Initial release",
"released_at": "2026-01-10T00:00:00Z",
"file_size": 230400,
"has_file": false,
"scan_status": "completed",
"threat_level": "clean",
"scan_report_url": null
}
]
}
}
| Field | Type | Description | |
|---|---|---|---|
version | string | Latest version (same as currentVersion) | |
currentVersion | string | Latest version (client compatibility alias) | |
tier_required | string | Product level (determines credit cost) | |
has_access | boolean | true for all paying subscribers, false for free tier | |
compatibility | object | WordPress/PHP/WooCommerce compatibility (may be null if not parsed) | |
compatibility.wp_requires | string | Minimum WordPress version required | |
compatibility.wp_tested | string | WordPress version tested up to | |
compatibility.php_requires | string | Minimum PHP version required | |
compatibility.wc_requires | string\ | null | Minimum WooCommerce version (WC extensions only) |
compatibility.wc_tested | string\ | null | WooCommerce version tested up to (WC extensions only) |
versions | array | Version history for rollback support | |
versions[].has_file | boolean | Whether version file is available for download. false for archived versions where the file has been removed from S3. The download endpoint rejects archived versions - disable rollback/install UI when false. | |
versions[].scan_status | string\ | null | Virus scan status: completed, scanning, error, skipped, unknown, or null |
versions[].threat_level | string\ | null | Threat assessment: clean, suspicious, malicious, or null |
versions[].scan_report_url | string\ | null | Full VirusTotal scan report URL (null if not scanned) |
Downloads
#### POST /api/v1/plugins/[slug]/download
Request download URL. Deducts credits from customer account (or free for version rollbacks).
Request:{
"version": "1.2.0",
"wpVersion": "6.7.1",
"phpVersion": "8.2.0",
"wcVersion": "9.5.1",
"bypassSuspicious": false
}
| Field | Type | Required | Description |
|---|---|---|---|
version | string | No | Specific version to download. Defaults to latest. |
wpVersion | string | No | Site's WordPress version (for compatibility check) |
phpVersion | string | No | Site's PHP version (for compatibility check) |
wcVersion | string | No | Site's WooCommerce version (for compatibility check) |
bypassSuspicious | boolean | No | If true, skip the suspicious file warning and proceed |
{
"success": true,
"downloadUrl": "https://...",
"expiresAt": "2026-01-23T13:00:00Z",
"creditsDeducted": 1,
"creditsRemaining": 88,
"version": "1.2.0",
"isRollback": false,
"warnings": [
{ "code": "INCOMPATIBLE_WP", "message": "Requires WordPress 6.8+, site runs 6.7.1" }
]
}
| Field | Type | Description |
|---|---|---|
downloadUrl | string | Signed S3 URL for download |
expiresAt | string | URL expiration timestamp |
creditsDeducted | number | Credits charged (0 for rollbacks) |
creditsRemaining | number | User's remaining credit balance |
version | string | Version being downloaded |
isRollback | boolean | True if downloading an older version |
warnings | array | Safety warnings (only present if non-empty). See Safety Gates below. |
402: Insufficient credits (INSUFFICIENT_CREDITS)403: Version rollback requires prior download (ROLLBACK_NOT_ELIGIBLE)403: Malicious file blocked (MALICIOUS_FILE) - see Safety Gates below404: Plugin not found (NOT_FOUND)404: Plugin file not available (FILE_NOT_AVAILABLE)
#### Version Rollback
Users can download previous versions for free if they have previously downloaded the plugin:
- Eligibility: User must have at least one prior download of the plugin
- Cost: Rollbacks are free (0 credits deducted)
- Tracking: Downloads are still recorded with
isRollback: truein store context
// Request older version
{ "version": "1.1.0" }
// Response (free rollback)
{
"success": true,
"downloadUrl": "https://...",
"creditsDeducted": 0,
"creditsRemaining": 88,
"version": "1.1.0",
"isRollback": true
}
> Credit Refunds: If download fails after credits are deducted (e.g., backend error), credits are automatically refunded.
> Note: Legacy endpoint /api/v1/plugins/[slug]/install also exists with slightly different response format. Use /download for new integrations.
Safety Gates (Pre-Install Checks)
All download and install endpoints run pre-install safety checks before deducting credits. Two types of checks:
#### Compatibility Checks
If the connector sends wpVersion, phpVersion, or wcVersion in the request body, the API validates them against the plugin's requirements (wpRequires, phpRequires, wcRequires). Incompatible versions produce warnings (non-blocking):
| Warning Code | Example Message |
|---|---|
INCOMPATIBLE_WP | "Requires WordPress 6.8+, site runs 6.7.1" |
INCOMPATIBLE_PHP | "Requires PHP 8.1+, site runs 7.4.33" |
INCOMPATIBLE_WC | "Requires WooCommerce 9.0+, site runs 8.5.2" |
Warnings are included in the warnings array of successful responses. The download/install still proceeds.
#### VirusTotal Threat Checks
Each plugin version carries a VirusTotal scan status. The API checks the version's threatLevel before allowing download:
| Threat Level | Behavior | HTTP Status |
|---|---|---|
clean | Proceed normally | 200 |
suspicious | Warning in response, proceed | 200 |
malicious | Blocked - install refused | 403 |
{
"success": true,
"warnings": [
{
"code": "SUSPICIOUS_FILE",
"message": "Version 2.1.0 has been flagged as suspicious by VirusTotal - proceed with caution. Report: https://..."
}
]
}
To bypass the suspicious warning (user confirmed), re-send with "bypassSuspicious": true.
{
"error": "Version 2.1.0 has been flagged as malicious by VirusTotal and cannot be installed. Report: https://...",
"code": "MALICIOUS_FILE",
"warnings": []
}
HTTP 403. Hard block - no bypass option. The connector should display the error with the VirusTotal report link.
#### Dashboard Remote Install
The dashboard install endpoint (POST /api/account/sites/[clientId]/plugins/install) runs the same safety checks but automatically fetches the site's WP/PHP versions via getRemoteSiteHealth() - no manual version passing needed.
Updates
#### POST /api/v1/plugins/check-updates
Check for available updates for installed plugins.
Request:{
"plugins": [
{ "slug": "plugin-a", "version": "1.0.0" },
{ "slug": "plugin-b", "version": "2.3.1" }
]
}
Response:
{
"updates": [
{
"slug": "plugin-a",
"currentVersion": "1.0.0",
"newVersion": "1.2.0",
"creditCost": 1,
"changelog": "### 1.2.0\n- ..."
}
]
}
Account
#### GET /api/v1/account
Get current account status including tier, credits, and subscription info.
Response:{
"email": "[email protected]",
"tier": "agency",
"tierLabel": "Agency",
"creditsAvailable": 88,
"creditsUsed": 12,
"creditsResetAt": "2026-02-01T00:00:00Z", // Next allocation date; null if cancelled (credits frozen)
"subscriptionStatus": "active"
}
#### GET /api/v1/credits
Alternative endpoint for credit balance only.
Response:{
"tier": "agency",
"subscription_status": "active",
"credits": {
"available": 88,
"used": 12,
"reset_at": "2026-02-01T00:00:00Z" // Next allocation date; null if cancelled (credits frozen)
},
"next_payment": "2026-02-01T00:00:00Z",
"top_up_url": "https://zeebrar.com/checkout/credits"
}
Site Disconnect
#### POST /api/v1/client/disconnect
Called by the gplc-connector plugin when the user disconnects from the WordPress side.
Deactivates the client record so it no longer counts toward the site limit and shows as revoked on the Zeebrar account page.
Auth: Bearer token (JWT from/client/auth)
Headers:
Authorization: Bearer <token>
Response (success):
{
"success": true,
"message": "Site disconnected successfully"
}
Response (already disconnected - idempotent):
{
"success": true,
"message": "Site already disconnected"
}
Errors: TOKEN_INVALID (401), DISCONNECT_ERROR (500)
Connector implementation: Call this endpoint when the user clicks "Disconnect" or "Log Out" in the WordPress plugin settings, before clearing local credentials. This ensures the site is properly freed from the account's site limit.
Site Revoke
#### POST /api/v1/client/revoke
Revoke access for a connected site from the Zeebrar dashboard. Sets the client to inactive and invalidates its API key. The site will show as "Revoked" on the account page and no longer count toward the site limit.
Auth: Session (user must be logged into Zeebrar dashboard) Request:{
"clientId": "client-uuid"
}
Response:
{
"success": true
}
Errors: UNAUTHORIZED (401), NOT_FOUND (404)
Side effects: Syncs revocation to GPLC via deleteClientSite (fire-and-forget).
Site Reconnect
#### POST /api/v1/client/reconnect
Reactivate a previously revoked site. Generates a new API key and syncs the client with the customer's current tier/credits. Returns the customer's license key so the user can re-enter it in WordPress.
Auth: Session (user must be logged into Zeebrar dashboard) Request:{
"clientId": "client-uuid"
}
Response (success):
{
"success": true,
"message": "Site reconnected. Re-enter your license key in the WordPress plugin to complete setup.",
"licenseKey": "zb_xxxxxxxxxxxxxxxx"
}
Errors:
404: Site not found or not eligible for reconnection403:SITE_LIMIT_REACHED- tier site limit exceeded
maxSites limit before reactivating. If the limit is reached, returns an error suggesting the user revoke another site or upgrade.
Site Remove
#### POST /api/v1/client/remove
Permanently delete a revoked site record. Only works on sites that are already revoked (isActive: false). Download history is preserved (FK set to null).
{
"clientId": "client-uuid"
}
Response:
{
"success": true
}
Errors: 404 - Site not found or still active (must revoke first)
Side effects: Syncs deletion to GPLC via deleteClientSite (fire-and-forget). Download records are detached (not deleted).
---
Error Format
All errors return:
{
"error": "Human-readable message",
"code": "ERROR_CODE"
}
Error codes:
| Code | HTTP Status | Description |
|---|---|---|
UNAUTHORIZED | 401 | Missing/invalid token |
FORBIDDEN | 403 | Access denied |
NOT_FOUND | 404 | Resource not found |
FILE_NOT_AVAILABLE | 404 | Plugin exists but file not uploaded |
INSUFFICIENT_CREDITS | 402 | Not enough credits |
MALICIOUS_FILE | 403 | VirusTotal flagged version as malicious (hard block) |
LICENSE_INVALID | 401 | Invalid license key |
LICENSE_EXPIRED | 403 | License key expired |
LICENSE_SUSPENDED | 403 | License suspended |
SITE_REVOKED | 403 | Site access was revoked by user |
SITE_LIMIT_REACHED | 403 | Plan site limit exceeded (admin-configurable via SubscriptionPlan.maxSites) |
RATE_LIMITED | 429 | Too many requests |
INTERNAL_ERROR | 500 | Server error |
Rate Limits
Limits are enforced per client IP (X-Forwarded-For) with a sliding window; a 429 carries Retry-After.
POST /api/v1/client/auth: 5 requests/minutePOST /api/v1/plugins/{slug}/download: 30 requests/minutePOST /api/v1/plugins/{slug}/install(dashboard-initiated): 20 requests/minute- Other v1 endpoints: no dedicated limiter today; the per-client, per-plugin 30-second duplicate-download guard still applies
Subscription Tiers
All paying subscribers can browse and install any plugin. Credits are the only gate.
| Tier | Display Name | Credits/mo | Site Limit |
|---|---|---|---|
| free | Free | 0 | 1 |
| personal | Personal | 75 | 5 |
| pro | Pro | 200 | 15 |
| agency | Agency | 600 | Unlimited |
> Note: Use tierLabel from API responses for user-facing display.
Credit Costs (Plugin Pricing)
Plugins have a product level (tier_required) that determines how many credits are required to download.
Credit costs are configured per-store in Zeebrar admin settings.
Current Zeebrar Credit Costs:| Product Level | Credits | Badge Color |
|---|---|---|
| Lite | 1 | Green |
| Pro | 3 | Blue |
| Geek | 5 | Purple |
| Exclusive | 10 | Amber |
/api/v1/plugins):
credits_required: Number of credits required (integer) - use this for displaytier_required: Product level (lite,pro,geek,exclusive) - determines credit costhas_access:truefor all paying subscribers,falsefor free tiercompatibility: Object with version requirements (may be null if not parsed):
wp_requires, wp_tested, php_requires - WordPress/PHP compatibility
- wc_requires, wc_tested - WooCommerce compatibility (only for WC extensions, null otherwise)
The connector should display the credit cost directly (not tier names):
// Example: Display credit badge in plugin list
$credits = $plugin['credits_required']; // e.g., 3
$has_access = $plugin['has_access']; // e.g., true
// Badge color based on credit count
$badge_class = match(true) {
$credits === 0 => 'badge-gray',
$credits === 1 => 'badge-green',
$credits <= 3 => 'badge-blue',
$credits <= 5 => 'badge-purple',
default => 'badge-amber',
};
// Display: "3 credits"
echo sprintf('<span class="%s">%d credit%s</span>',
$badge_class,
$credits,
$credits !== 1 ? 's' : ''
);
// Compatibility badges
$compat = $plugin['compatibility'] ?? null;
if ($compat) {
if (!empty($compat['wp_requires'])) {
echo '<span class="badge-blue">WP ' . $compat['wp_requires'] . '+</span>';
}
if (!empty($compat['wp_tested'])) {
echo '<span class="badge-green">Tested ' . $compat['wp_tested'] . '</span>';
}
if (!empty($compat['php_requires'])) {
echo '<span class="badge-purple">PHP ' . $compat['php_requires'] . '+</span>';
}
// WooCommerce badges (only for WC extensions)
if (!empty($compat['wc_requires'])) {
echo '<span class="badge-violet">WC ' . $compat['wc_requires'] . '+</span>';
}
if (!empty($compat['wc_tested'])) {
echo '<span class="badge-fuchsia">WC Tested ' . $compat['wc_tested'] . '</span>';
}
}
Compatibility Badge Colors:| Field | Badge Color | Example |
|---|---|---|
wp_requires | Blue | "WP 6.0+" |
wp_tested | Green | "Tested 6.7" |
php_requires | Purple | "PHP 7.4+" |
wc_requires | Violet | "WC 8.0+" |
wc_tested | Fuchsia | "WC Tested 9.0" |
wc_requires or wc_tested are WooCommerce extensions. You may display a "WooCommerce Extension" label for these plugins.
Admin Support Endpoints
Endpoints for Zeebrar admin staff to support customer sites.
GET /api/admin/clients/{clientId}/diagnostics
Fetch diagnostics from a customer's WordPress site for support troubleshooting.
Authentication: Admin session required Query Parameters:| Param | Type | Default | Description |
|---|---|---|---|
include_connectivity | boolean | false | Include connectivity tests (slower) |
error_limit | int | 20 | Max errors to return (1-100) |
{
"success": true,
"client": {
"id": "client-uuid",
"siteUrl": "https://customer-site.com",
"siteName": "Customer Site"
},
"diagnostics": {
"environment": {
"wordpress_version": "6.7",
"php_version": "8.2",
"plugin_version": "1.0.5",
"site_url": "https://customer-site.com",
"is_multisite": false,
"active_theme": "Astra",
"memory_limit": "256M",
"max_execution_time": 30
},
"gplc_state": {
"connected": true,
"email": "[email protected]",
"tier": "agency",
"credits_available": 89,
"credits_used": 11,
"subscription_status": "active",
"api_url": "https://zeebrar.com",
"last_auth": "2026-01-20T14:00:00Z",
"token_valid": true,
"installed_plugins": 12,
"installed_themes": 3
},
"sync_health": {
"last_sync": "2026-01-20T14:00:00Z",
"syncs_24h": 24,
"failed_syncs_24h": 0,
"average_sync_time": 150
},
"errors": [
{
"timestamp": "2026-01-20T12:00:00Z",
"type": "download_failed",
"message": "Connection timeout",
"context": "plugin-slug v1.2.0"
}
],
"performance": {
"php_memory_usage": 52428800,
"php_memory_peak": 67108864,
"db_queries": 42,
"db_query_time": 0.125
},
"connectivity": {
"zeebrar_api": { "reachable": true, "latency_ms": 120 },
"ssl_valid": true,
"curl_available": true
}
},
"fetchedAt": "2026-01-20T15:30:00Z"
}
Errors:| Code | HTTP Status | Description |
|---|---|---|
UNAUTHORIZED | 401 | Not logged in as admin |
NOT_FOUND | 404 | Client not found |
CLIENT_INACTIVE | 400 | Client site is inactive |
CLIENT_REVOKED | 400 | Client access was revoked |
FETCH_FAILED | 502 | Failed to fetch from client site |
TIMEOUT | 504 | Request timed out (>15s) |
CONNECTION_FAILED | 502 | Could not connect to client site |
/admin/customers/[id]) to fetch live diagnostics from connected WordPress sites for support troubleshooting.
How it works:
- Admin clicks "Diagnostics" button on a connected site
- Zeebrar calls the client's WordPress site at
GET /wp-json/gplc/v1/diagnostics - Request is authenticated using the client's stored API key (
X-GPLC-API-Keyheader) - Response is returned to admin UI for display
WordPress Plugin Integration
The gplc-connector plugin uses this API:
// License key authentication (preferred)
$response = wp_remote_post(ZEEBRAR_API_URL . '/v1/client/auth', [
'headers' => ['Content-Type' => 'application/json'],
'body' => json_encode([
'licenseKey' => $license_key,
'siteUrl' => home_url(),
'siteTitle' => get_bloginfo('name')
])
]);
// Subsequent requests with token
$response = wp_remote_get(ZEEBRAR_API_URL . '/v1/plugins', [
'headers' => [
'Authorization' => 'Bearer ' . $token,
'Content-Type' => 'application/json'
]
]);
---
Last updated: 2026-02-27 (added safety gates: compatibility checks, VirusTotal threat blocking, warnings array)