Zodiac Tech CRM Docs

API overview

The two APIs Zodiac Tech CRM exposes — the REST API for servers and the Mobile API for the Android app — how they authenticate, version, and rate-limit.

developer admin super-admin Medium ⏱ 10 min Updated 2026-10-06

Zodiac Tech CRM exposes two separate HTTP APIs:

API Base path Who it is for Authentication
REST API /api/v1 Servers, no-code tools (Zapier, Pabbly, n8n), custom scripts Per-workspace API keys (Authorization: Bearer lh_...)
Mobile API /api/v1/mobile The ZodiacTech CRM Android app (com.zodiactech.crm) Per-user device tokens (Authorization: Bearer <token>)

They share the same Laravel application but have different auth, different response envelopes and different rate limits. Pick the one that matches your client:

Who this is for

  • Developer — building an integration against the REST API or debugging the mobile app's traffic.
  • Admin — creating and scoping API keys for your workspace.
  • Super-admin — monitoring API health and platform-level rate limits.

Prerequisites: a working install (see Go-live checklist) reachable over HTTP(S).

Base URLs

The exact base URL depends on how the install is deployed. When the domain's document root points at the public/ folder (the recommended setup on cPanel), there is no subfolder in the URLs:

Production:   https://crm.zodiactechsoft.in/api/v1          (REST)
              https://crm.zodiactechsoft.in/api/v1/mobile/  (Mobile)

If the app runs inside a subfolder — for example the XAMPP development install at http://localhost/Lead%20Hub/, where the root .htaccess bridge forwards requests into public/ — the subfolder is part of the path:

Local dev:    http://localhost/Lead%20Hub/api/v1
              http://localhost/Lead%20Hub/api/v1/mobile/

Which layout am I running? If https://your-domain.com/ opens the CRM directly, the document root is pointed at public/ and no subfolder appears in API URLs. If https://your-domain.com/somefolder/ is the CRM, include somefolder in every API URL.

Versions

The API version is read from config('leadhub.api_version'), set by the LEADHUB_API_VERSION environment variable (default v1). The canonical surface is /api/v1/*.

Two compatibility behaviours exist for convenience:

  1. Unversioned lead aliases — GET/POST /api/leads (plus /api/leads/{id}) work exactly like /api/v1/leads. These exist because some no-code platforms (Pabbly Connect, older Zapier connectors, n8n HTTP nodes) do not follow 308 redirects on POST.
  2. Automatic 308 redirect — any other path under /api/ that does not match an explicit route is redirected to its /api/v1/ equivalent, preserving the HTTP method and body. Unknown paths return a JSON 404 ({"status":"error","message":"Endpoint not found.","code":"NOT_FOUND"}).

Interactive documentation

The full OpenAPI 3.1.0 specification renders as Swagger UI — no login required:

  • /api/docs (canonical)
  • /api/documentation and /api/swagger (aliases)
  • /api/v1/docs (versioned alias)

The spec documents API-key scopes, the pagination envelope, and the outbound webhook events (plus a virtual x-webhooks endpoint).

Authentication at a glance

Client Header Credential created in Lifetime
REST API Authorization: Bearer lh_... Admin panel → Settings → API Keys Set per key (expires_at optional)
Mobile API Authorization: Bearer <64-char token> Minted at POST /api/v1/mobile/login 90 days, revoked on logout

Detailed flows: Authentication. Error handling for both: Error codes & handling.

Rate limits

Rate limiting is enforced by middleware, per API key and per workspace:

Limit Source Default
Per-key hourly limit api_keys.rate_limit_per_hour (set on the key) 1000 requests/hour
Workspace hourly ceiling tenants.settings.api.rate_limit_per_hour, else a multiple of the per-key limit 5× the per-key limit

The multiplier comes from config('leadhub.api.tenant_rate_limit_multiplier') (LH_API_TENANT_RL_MULTIPLIER). Both are fixed hourly windows — the counter resets on the hour boundary.

Successful responses carry the per-key headers:

X-RateLimit-Limit / X-RateLimit-Remaining / X-RateLimit-Reset

When the per-key limit is exceeded the API returns 429 with those same headers plus Retry-After and Rate limit exceeded. Limit: N requests/hour. A workspace-level 429 instead carries the tenant-suffixed headers — X-RateLimit-Limit-Tenant / X-RateLimit-Remaining-Tenant / X-RateLimit-Reset-Tenant — plus Retry-After and Tenant rate limit exceeded. Limit: N requests/hour.

Other endpoints have their own, separate limiter:

Endpoint group Limit
Inbound lead intake (/api/inbound/leads, /api/v1/inbound) — Bearer tenant API key + ?tenant={slug} 120/min per IP
Token webhook receiver (/api/webhook/...) 120/min per IP
Public form submit (/api/forms/{tenant}/{slug}/submit) 60/min per IP
Messaging webhooks (/api/messaging/...) 120/min per IP
Commercial payment webhooks (`/api/webhooks/razorpay cashfree`)
Mobile login / OTP (/api/v1/mobile/login, otp/*) 10/min per IP (plus per-identifier limits)

Health check

GET /api/v1/health is public and never requires a key. It round-trips the database, cache and storage — each reported as a boolean in checks — and reports the configured queue connection name in queue:

{
  "status": "ok",
  "checks": { "db": true, "cache": true, "storage": true },
  "queue": "database",
  "version": "2.0.12",
  "timestamp": "2026-10-06T09:00:00+05:30"
}

status is ok or degraded; a degraded install returns HTTP 503. Failures are also written to the Laravel log (healthcheck.db.failed, and equivalents). The admin panel exposes the same probe at /up. Point your uptime monitor at this URL — see Monitoring & alerts.

Response shapes

  • REST API success: {"data": ...}, wrapped with meta (pagination: current_page, last_page, per_page, total, from, to) and links (first/last/prev/next/self) for lists. Errors: {"status":"error","message":"...","code":"..."}.
  • Mobile API success: {"data": ...} (plus meta for paginated lists). Errors: {"error":{"message":"...","code":"..."}}.

Common mistakes

  • Using an API key against /api/v1/mobile/* — device tokens and API keys are not interchangeable (and vice versa).
  • Forgetting the subfolder when the install is not document-rooted at public/ — the request 404s at the web-server level before Laravel sees it.
  • Letting a key's expires_at pass silently — revoked/expired keys return 401 Invalid or expired API key.

Troubleshooting

Symptom Likely cause
401 API key required Missing or malformed Authorization header
403 Insufficient scope The key was created without the scope the endpoint needs
403 This workspace is not active The tenant's subscription is suspended or restricted
429 on every call Per-key limit too low, or a runaway loop shared across the workspace ceiling
404 Endpoint not found. JSON Path is wrong (and not merely unversioned)

Technical details

  • Routes: routes/api.php (REST, mobile, public webhook endpoints).
  • REST middleware: App\Http\Middleware\ApiKeyAuthentication.
  • Mobile middleware: App\Http\Middleware\DeviceAuthenticate.
  • Error payloads: App\Http\Responses\ApiErrorResponse.
  • Pagination resource: App\Http\Resources\Api\ApiResource.
  • Health: App\Http\Controllers\Api\HealthController.
  • OpenAPI viewer: App\Http\Controllers\Api\ApiDocsController.

Was this page helpful?