Laravel CRM
Log in Get Started

API

Overview

Laravel CRM ships with a JSON REST API for partner developers and external integrations. The API is versioned, authenticated with Laravel Sanctum personal access tokens, and mounted at /crm/api/v2.

  • Base URL: https://your-app.test/crm/api/v2
  • Content type: application/json
  • Authentication: Sanctum bearer tokens
  • Current version: v2

The API exposes 8 resourceful entities with full CRUD (list, create, show, update, soft-delete), plus 3 auth endpoints for issuing, inspecting, and revoking tokens.

Entity references

Each entity has its own reference page covering endpoints, list parameters, JSON shape, and writable fields:

Requirements

  • Laravel CRM installed and migrated.
  • Laravel Sanctum installed in the host application.
  • A CRM user with crm_access granted, to issue tokens against.

Installation

The API ships with the package — you only need to wire Sanctum into the host application.

1. Publish and run Sanctum migrations

php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate

This creates the personal_access_tokens table.

2. Add HasApiTokens to your User model

use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens;
    // ...
}

3. Verify the API routes are registered

php artisan route:list --path=crm/api

You should see 8 resourceful entities (5 verbs each), plus the 3 auth routes.

Authentication

The API uses Sanctum personal access tokens. Tokens can be issued through the API itself, or via an artisan command for ops use.

Issue a token via the API

POST /crm/api/v2/auth/token
Content-Type: application/json

{
  "email": "user@example.com",
  "password": "secret",
  "device_name": "Mobile App"
}

Response (201 Created):

{
  "token": "1|abcdef1234...",
  "user": {
    "id": 1,
    "name": "Jane Doe",
    "email": "user@example.com"
  }
}
  • Returns 422 on bad credentials, an unknown email, or when the user lacks crm_access. The response is intentionally indistinguishable across these cases to avoid leaking which emails belong to real users.
  • device_name is optional and defaults to the request's User-Agent or api-token.
  • The plaintext token is shown once — store it securely.

Issue a token via artisan

php artisan laravel-crm:api-token user@example.com --name="Mobile App"

The plaintext token is printed once. The command exits non-zero if the user does not exist or lacks crm_access.

Making authenticated requests

Pass the token in the Authorization header:

GET /crm/api/v2/leads HTTP/1.1
Authorization: Bearer 1|abcdef1234...
Accept: application/json

Inspecting the current user

GET /crm/api/v2/auth/me

Response (200 OK):

{
  "user": {
    "id": 1,
    "name": "Jane Doe",
    "email": "user@example.com"
  }
}

Revoking the current token

DELETE /crm/api/v2/auth/token

Returns 204 No Content and deletes the personal access token used to authenticate the request.

Headers

Header Required Purpose
Authorization: Bearer <token> Yes (except POST /auth/token) Sanctum personal access token.
Accept: application/json Recommended Forces JSON responses (the laravel-crm.api.json middleware sets this automatically when missing).
Content-Type: application/json Yes (for POST / PUT) Request body is JSON.
X-Team-ID: <team-id> Optional Overrides the authenticated user's active team for the request. Must be a team the user belongs to; otherwise the API returns 403. Only relevant when laravel-crm.teams=true.

Multi-tenancy notes

When the host app runs in teams mode (config('laravel-crm.teams', true)):

  • Without X-Team-ID, requests are scoped to the user's current_team_id.
  • With X-Team-ID, list / store / update / delete endpoints run in the context of that team.
  • GET /{resource}/{uuid} resolves the route-bound model using the user's default current team, because Laravel's SubstituteBindings middleware runs before the team-context middleware. Use the list endpoints (filtered by X-Team-ID) to discover the correct UUIDs for the active team.

Endpoint summary

Auth

Method Path Auth Description
POST /crm/api/v2/auth/token Public Issue a personal access token.
GET /crm/api/v2/auth/me Bearer Return the authenticated user.
DELETE /crm/api/v2/auth/token Bearer Revoke the current token.

Entities

All entity endpoints follow the same RESTful shape:

Verb Path Action
GET /{resource} List (paginated).
POST /{resource} Create.
GET /{resource}/{uuid} Show.
PUT /{resource}/{uuid} Update.
DELETE /{resource}/{uuid} Soft-delete.

{uuid} is the entity's external_id (UUID), exposed as id in JSON responses.

Resource Path Reference
Lead /crm/api/v2/leads Leads
Product /crm/api/v2/products Products
Organization /crm/api/v2/organizations Organizations
Person /crm/api/v2/people People
Deal /crm/api/v2/deals Deals
Quote /crm/api/v2/quotes Quotes
Order /crm/api/v2/orders Orders
Invoice /crm/api/v2/invoices Invoices

Conventions

  • IDs are UUIDs. The JSON id is always the entity's external_id. Integer primary keys are never exposed.
  • Foreign keys are also UUIDs for CRM entities — e.g. person_id, organization_id, lead_id, pipeline_stage_id, lead_source_id, product_category_id, and every labels[] entry must be a UUID (the related row's external_id).
  • Some lookups are integer IDs. Host-app users (user_owner_id) and a handful of small reference tables that aren't UUID-backed use integer primary keys: tax_rate_id, organization_type_id, industry_id, timezone_id.
  • Human-readable IDs are returned, not accepted. Entities also expose a sequential identifier (lead_id, deal_id, quote_id, order_id, invoice_id, e.g. L1001, D1001) in the response. These are read-only and assigned automatically.
  • Money is dollars in JSON; cents in storage. All amount / price / total / subtotal / discount / tax / adjustments fields are sent and returned as decimal dollars (e.g. 1500.50). The package converts to integer cents on write.
  • Timestamps are ISO-8601 and must include a timezone offset or the Z UTC suffix on input — e.g. 2026-07-15T10:00:00+00:00 or 2026-07-15T10:00:00Z. Other formats are rejected with a 422.
  • Pagination: ?per_page=N (1–100, default 25). Responses use Laravel's standard pagination envelope (data, meta, links).
  • Sorting: ?sort=field ascending; ?sort=-field descending. Unknown columns are silently ignored. Default sort is -created_at.
  • Soft deletes: DELETE returns 204 and soft-deletes the row. Subsequent GETs return 404.

Errors

The API returns standard Laravel error envelopes.

422 Unprocessable Entity (validation)

{
  "message": "The given data was invalid.",
  "errors": {
    "title": ["The title field is required."],
    "person_id": ["The selected person_id is invalid."]
  }
}

401 Unauthorized

{ "message": "Unauthenticated." }

403 Forbidden

{ "message": "This action is unauthorized." }

For the X-Team-ID non-member case:

{ "message": "You are not a member of the requested team." }

404 Not Found

Returned when a UUID does not resolve to a model (or it has been soft-deleted).

429 Too Many Requests

Returned when the rate limit is exceeded. Standard Laravel rate-limit headers are included: X-RateLimit-Limit, X-RateLimit-Remaining, and Retry-After.

Rate limits

The API enforces a single named rate limiter, laravel-crm-api:

Caller Limit
Authenticated (Sanctum) 60 requests / minute / user
Unauthenticated 30 requests / minute / IP

Exceeding the limit returns 429 Too Many Requests with Retry-After in seconds.

Worked example

Issue a token, list leads, create a lead, then revoke the token.

# 1. Issue a token
curl -s -X POST https://example.test/crm/api/v2/auth/token \
  -H "Content-Type: application/json" \
  -d '{"email":"user@example.com","password":"secret","device_name":"curl"}' \
  | jq .

# 2. List leads
TOKEN="1|abcdef..."
curl -s https://example.test/crm/api/v2/leads \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json" \
  | jq .

# 3. Create a lead
curl -s -X POST https://example.test/crm/api/v2/leads \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "New enterprise lead",
    "amount": 12500.00,
    "currency": "USD",
    "expected_close": "2026-09-30T00:00:00Z"
  }' \
  | jq .

# 4. Revoke the token
curl -s -X DELETE https://example.test/crm/api/v2/auth/token \
  -H "Authorization: Bearer $TOKEN" \
  -i