Zodiac Tech CRM Docs

REST API (API keys)

Complete reference for every /api/v1 endpoint — method, path, required scope, key parameters and response summary, grouped by resource.

developer admin Advanced ⏱ 20 min Updated 2026-10-06

This is the complete REST API surface. Every endpoint lives under /api/v1 (plus a friendly unversioned alias for lead CRUD), authenticates with an API key, and enforces one scope per route.

Base URL https://crm.zodiactechsoft.in/api/v1 in production. On a subfolder install the subfolder is part of the path (http://localhost/Lead%20Hub/api/v1). Append the path from the tables below.

All requests need:

Authorization: Bearer lh_yourKeyHere
Accept: application/json

List responses use the pagination envelope (data + meta + links); single-record responses are {"data": {...}}; errors follow Error codes & handling.

Who this is for

  • Developer — integrating a system, no-code tool or script against the CRM.
  • Admin — checking which scope a key needs before creating it.

Prerequisites: an API key with the relevant scopes (Authentication).


Health

Method Path Scope Purpose
GET /health none (public) DB/cache/storage/queue check, version, timestamp. 200 ok or 503 degraded

Leads

Scopes: read:leads, write:leads, delete:leads.

Method Path Scope Purpose
GET /leads read:leads List leads (paginated, filterable)
POST /leads write:leads Create a lead — 201 on success; 402 if the plan's lead quota is exhausted
GET /leads/{id} read:leads Single lead with tags, assigned user, pipeline stage
PUT / PATCH /leads/{id} write:leads Update a lead
DELETE /leads/{id} delete:leads Delete a lead (soft delete)
POST /leads/{id}/tags write:leads Attach tags (creates missing tags)
DELETE /leads/{id}/tags/{tag} write:leads Detach one tag by id or name

List filters (query string): source, status, pipeline_id, stage_id, assigned_user_id, tag (name), search (matches first/last name, email, phone), created_after, created_before, starred=1, sort (one of created_at, updated_at, lead_score, first_name, last_name, email; default created_at), direction (asc/desc, default desc), per_page (default 15, max 100).

Create body (real fields):

{
  "first_name": "Aarav",
  "last_name": "Shah",
  "email": "aarav@example.com",
  "phone": "+91 98xxxxxx21",
  "source": "api",
  "status": "new",
  "pipeline_id": 1,
  "pipeline_stage_id": 4,
  "assigned_user_id": 7,
  "notes": "Interested in the 3BHK project",
  "custom_fields": { "budget": "80L" },
  "tags": ["website", "hot"]
}

first_name is required; email max 255, phone max 50, source/status max 80/50. pipeline_id, pipeline_stage_id and assigned_user_id are validated to belong to this workspace (cross-tenant ids fail 422).

Tag attach body: {"tags": ["hot", "mumbai"]} → {"data": [...], "message": "2 tag(s) attached."}. Tag detach accepts an id or a name in the URL segment.

Unversioned aliases

The same lead CRUD is also exposed without the version prefix for tools that mishandle 308 redirects: GET/POST /api/leads, GET/PUT/PATCH/DELETE /api/leads/{id}, POST /api/leads/{id}/tags, DELETE /api/leads/{id}/tags/{tag}. Same scopes, same behaviour.

Pipelines & stages

Scopes: read:pipelines, write:pipelines (stage routes are covered by the pipelines scope).

Method Path Purpose
GET /pipelines List pipelines
POST /pipelines Create — body: name (required, max 100), description (optional), is_default (bool)
GET /pipelines/{id} Pipeline detail
PUT / PATCH /pipelines/{id} Update
DELETE /pipelines/{id} Delete
GET /pipelines/{id}/stages List stages of a pipeline
POST /pipelines/{id}/stages Create stage — body: name (required, max 100), color, sort_order, is_won, is_lost
GET /pipelines/{id}/stages/{stage} Stage detail
PUT / PATCH /pipelines/{id}/stages/{stage} Update
DELETE /pipelines/{id}/stages/{stage} Delete — blocked with 422 Cannot delete stage with leads. Move leads first. if leads sit in the stage

Tags

Scopes: read:tags, write:tags.

Method Path Purpose
GET /tags List tags
POST /tags Create — body: name (required, max 80), color
GET /tags/{id} Tag detail
PUT / PATCH /tags/{id} Update
DELETE /tags/{id} Delete

Users

Scopes: read:users, write:users, delete:users.

Method Path Scope Purpose
GET /users read:users List workspace users
POST /users write:users Create — body: name, email (unique), password (min 8), timezone, role (admin or member)
GET /users/{id} read:users User detail
PUT / PATCH /users/{id} write:users Update
DELETE /users/{id} delete:users Delete

Forms

Scopes: read:forms, write:forms, delete:forms.

Method Path Scope Purpose
GET /forms read:forms List forms
POST /forms write:forms Create — body: name (required), title, description, submit_label, thank_you_message, redirect_url, pipeline_id, pipeline_stage_id, active, multi_step
GET /forms/{id} read:forms Form detail
PUT / PATCH /forms/{id} write:forms Update
DELETE /forms/{id} delete:forms Delete
POST /forms/{id}/submissions write:forms Submit an entry — body: {"fields": {"name": "...", "email": "..."}}; creates a lead through the same pipeline as the public form

Automations

Scopes: read:automations, write:automations, delete:automations.

Method Path Scope Purpose
GET /automations read:automations List automations
POST /automations write:automations Create — body: name, description, enabled, trigger_type (must be a supported trigger), trigger_config (object)
GET /automations/{id} read:automations Detail
PUT / PATCH /automations/{id} write:automations Update
DELETE /automations/{id} delete:automations Delete

Integrations

Scopes: read:integrations, write:integrations (write covers create, update and delete).

Method Path Scope Purpose
GET /integrations/types read:integrations List supported integration types
GET /integrations read:integrations List connections
POST /integrations write:integrations Create — body: type (must be a supported type, else 422 Unsupported integration type.), name, enabled, config (stored encrypted), field_mapping, filter_config
GET /integrations/{id} read:integrations Detail
PUT / PATCH /integrations/{id} write:integrations Update
DELETE /integrations/{id} write:integrations Delete

Documentation endpoints (no auth)

Path Purpose
/api/docs Swagger UI (canonical)
/api/documentation, /api/swagger Aliases of the same UI
/api/v1/docs Versioned alias

Public (token/anon) endpoints — outside /api/v1

Most authenticate by URL token or provider signature; inbound lead intake instead uses a Bearer tenant API key. They are documented in full on Webhook endpoints.

Method Path Rate limit Purpose
POST /api/inbound/leads and /api/v1/inbound 120/min/IP Lead intake for Zapier/n8n-style callers — Authorization: Bearer {tenant api_key} + ?tenant={slug}
POST /api/webhook/{tenant}/{source}/{token} 120/min/IP Token-based generic inbound webhook receiver
POST /api/forms/{tenant}/{slug}/submit 60/min/IP Public form submission
POST / GET /api/messaging/{channel}/webhook/{tenant}/{token} 120/min/IP WhatsApp/SMS/Telegram/Viber inbound + verification handshake
POST /api/voice/{tenant}/{twiml|status|recording}/{token} 120/min/IP Twilio voice callbacks
POST /api/webhooks/razorpay and /api/webhooks/cashfree 240/min/IP Commercial billing provider webhooks

Inbound lead intake: authenticate with the workspace's tenant API key as a Bearer token and name the same workspace in ?tenant={slug} — a missing key returns 401, an unknown or inactive tenant slug 404, and a key that does not match that tenant 401. email is required; accepted fields: first_name, last_name, phone, company, source, notes, tags[], custom_fields. A new lead returns 201 {"status":"created","lead_id":...}; an email that already exists returns 200 {"status":"duplicate","lead_id":...}.

Version fallback behaviour

Any other /api/<path> request is 308-redirected to /api/v1/<path> (method and body preserved). Paths whose first segment is already a root surface (v1, docs, documentation, swagger, leads, inbound, webhook, webhooks, forms, messaging, voice) are excluded and return JSON 404 {"status":"error","message":"Endpoint not found.","code":"NOT_FOUND"}.

Common mistakes

  • Requesting /api/v1/leads/{id} with a lead id from another workspace — returns 404 by design.
  • Sending status as an array — it is a string filter here.
  • Using PUT with partial data — PUT validates like PATCH (sometimes rules), but send the full object when in doubt.
  • Forgetting Accept: application/json — Laravel then renders HTML error pages that break JSON parsers (message parsing still works, but use the header).

Troubleshooting

Symptom Check
403 with a scope name Key scopes vs the table above
422 "does not belong to your workspace" pipeline_id / stage_id / assigned_user_id from another tenant
402 on create Plan lead quota — inbound capture paths still work; only manual/API creation is blocked
Stage delete fails Move leads out of the stage first

Technical details

  • Routes: routes/api.php; controllers: App\Http\Controllers\Api\V1\*.
  • Resources: App\Http\Resources\Api\{LeadResource, TagResource, ApiResource}.
  • Scope middleware: api.key:<scope> → App\Http\Middleware\ApiKeyAuthentication.
  • Exact validation rules live in each controller (LeadController, PipelineController, StageController, TagController, UserController, FormController, AutomationController, IntegrationController).

Was this page helpful?