# API Reference for Front-end Developers **Last Updated:** 2026-04-25 **Version:** 1.0.0 --- ## 📋 Table of Contents 1. [Overview](#overview) 2. [Authentication](#authentication) 3. [Base URL](#base-url) 4. [Response Format](#response-format) 5. [Error Handling](#error-handling) 6. [Customers API](#customers-api) 7. [Contacts API](#contacts-api) 8. [Contact Sharing API](#contact-sharing-api) 9. [Type Definitions](#type-definitions) 10. [Usage Examples](#usage-examples) --- ## Overview This API provides type-safe access to the CRM system using ElysiaJS. All endpoints return consistent responses with built-in error handling. **Key Features:** - ✅ Type-safe with TypeScript - ✅ Bearer token authentication via Keycloak - ✅ Consistent response format - ✅ Automatic error handling - ✅ Multi-tenant support (branch-scoped) --- ## Authentication All API endpoints require authentication via Bearer token from Keycloak. ### How it works: 1. Front-end authenticates with Keycloak 2. Token is stored in `window.__KEYCLOAK_TOKEN__` 3. API client automatically adds `Authorization: Bearer {token}` header 4. Token refresh is handled automatically on 401 errors ### Example: ```typescript // Token is automatically added by api-client.ts import { apiClient } from "@/lib/api-client"; const response = await apiClient("/customers"); // Authorization header is added automatically ``` --- ## Base URL ``` Development: http://localhost:3000/api Production: {TBD} ``` --- ## Response Format ### Success Response ```typescript { success: true, data: T, // The actual data message?: string, // Optional success message count?: number // Optional count for list responses } ``` ### Error Response ```typescript { success: false, error: string, // Error message details?: string // Optional detailed error info } ``` --- ## Error Handling ### HTTP Status Codes | Status | Description | | ------ | ------------------------------------ | | 200 | Success | | 400 | Bad Request (invalid input) | | 401 | Unauthorized (token expired/invalid) | | 403 | Forbidden (insufficient permissions) | | 404 | Not Found | | 500 | Internal Server Error | ### Error Response Structure ```typescript { success: false, error: "Customer not found or access denied", details: "Customer ID 'xyz' does not exist" } ``` ### Handling Errors in Front-end ```typescript try { const response = await apiClient("/customers/123"); if (!response.success) { // Handle error console.error(response.error); return; } // Success - use response.data console.log(response.data); } catch (error) { // Network error or unexpected error console.error("API call failed:", error); } ``` --- ## Customers API ### 1. Get All Customers Get all customers for the current user's branch. **Endpoint:** `GET /customers` **Query Parameters:** - `status` (optional) - Filter by status: `active`, `inactive`, `pending` **Request:** ```typescript GET /customers GET /customers?status=active ``` **Response:** ```typescript { success: true, data: Customer[], count: 10, message: "Found 10 customer(s)" } ``` **Example:** ```typescript import { apiClient } from "@/lib/api-client"; import type { CustomerListResponse } from "@/types/api"; const response = await apiClient( "/customers?status=active", ); ``` --- ### 2. Get Single Customer Get a specific customer by ID. **Endpoint:** `GET /customers/:id` **Path Parameters:** - `id` (required) - Customer ID **Request:** ```typescript GET /customers/123e4567-e89b-12d3-a456-426614174000 ``` **Response:** ```typescript { success: true, data: Customer } ``` **Example:** ```typescript const response = await apiClient(`/customers/${customerId}`); ``` --- ### 3. Create Customer Create a new customer. **Endpoint:** `POST /customers` **Request Body:** ```typescript { name: string, // Required email: string, // Required (must be valid email) phone: string, // Required company: string, // Required address: string, // Required customerStatus?: string, // Optional customerType?: string, // Optional taxId?: string // Optional } ``` **Response:** ```typescript { success: true, data: Customer, message: "Customer created successfully" } ``` **Example:** ```typescript import type { CreateCustomerRequest } from "@/types/api"; const newCustomer: CreateCustomerRequest = { name: "āļŠāļĄāļŠāļēāļĒ āđƒāļˆāļ”āļĩ", email: "somchai@example.com", phone: "081-234-5678", company: "āļšāļĢāļīāļĐāļąāļ— āđ„āļ—āļĒāļ˜āļļāļĢāļāļīāļˆ āļˆāļģāļāļąāļ”", address: "123 āļ–āļ™āļ™āļŠāļļāļ‚āļļāļĄāļ§āļīāļ— āđāļ‚āļ§āļ‡āļ„āļĨāļ­āļ‡āļ•āļąāļ™ āđ€āļ‚āļ•āļ„āļĨāļ­āļ‡āđ€āļ•āļĒ āļāļĢāļļāļ‡āđ€āļ—āļžāļŊ 10110", customerStatus: "active", customerType: "corporate", taxId: "0105551234567", }; const response = await apiClient("/customers", { method: "POST", body: JSON.stringify(newCustomer), }); ``` --- ### 4. Update Customer Update an existing customer. **Endpoint:** `PUT /customers/:id` **Path Parameters:** - `id` (required) - Customer ID **Request Body:** ```typescript { name?: string, email?: string, phone?: string, company?: string, address?: string, customerStatus?: string, erpCustomerCode?: string // For ERP sync } ``` **Response:** ```typescript { success: true, data: Customer, message: "Customer updated successfully" } ``` **Example:** ```typescript const updates = { erpCustomerCode: "ERP-001234", }; const response = await apiClient( `/customers/${customerId}`, { method: "PUT", body: JSON.stringify(updates), }, ); ``` --- ### 5. Delete Customer Delete a customer (soft delete). **Endpoint:** `DELETE /customers/:id` **Path Parameters:** - `id` (required) - Customer ID **Response:** ```typescript { success: true, data: Customer, message: "Customer deleted successfully" } ``` **Example:** ```typescript const response = await apiClient( `/customers/${customerId}`, { method: "DELETE", }, ); ``` --- ## Contacts API ### 1. Get Contacts for Customer Get all visible contacts for a customer. **Endpoint:** `GET /customers/:customerId/contacts` **Path Parameters:** - `customerId` (required) - Customer ID **Visibility Rules:** A contact is visible if: 1. Created by the current user, OR 2. Marked as public (`isPublic: true`), OR 3. Shared with the current user via `contact_shares` **Response:** ```typescript { success: true, data: Contact[], count: 5, message: "Found 5 contact(s)" } ``` **Example:** ```typescript const response = await apiClient( `/customers/${customerId}/contacts`, ); ``` --- ### 2. Create Contact Create a new contact for a customer. **Endpoint:** `POST /customers/:customerId/contacts` **Path Parameters:** - `customerId` (required) - Customer ID **Request Body:** ```typescript { name: string, // Required position?: string, // Optional phone?: string, // Optional mobile?: string, // Optional email?: string, // Optional isPrimary?: boolean, // Optional (default: false) notes?: string // Optional } ``` **Response:** ```typescript { success: true, data: Contact, message: "Contact created successfully" } ``` **Example:** ```typescript const newContact: CreateContactRequest = { name: "āļ§āļīāļ āļēāļ§āļĩ āļŠāļļāļ‚āļŠāļąāļ™āļ•āđŒ", position: "āļœāļđāđ‰āļˆāļąāļ”āļāļēāļĢāļāđˆāļēāļĒāļˆāļąāļ”āļ‹āļ·āđ‰āļ­", phone: "02-123-4567", mobile: "089-876-5432", email: "wipavi@example.com", isPrimary: true, notes: "Key decision maker", }; const response = await apiClient( `/customers/${customerId}/contacts`, { method: "POST", body: JSON.stringify(newContact), }, ); ``` --- ### 3. Update Contact Update an existing contact. **Endpoint:** `PUT /contacts/:contactId` **Path Parameters:** - `contactId` (required) - Contact ID **Rules:** - Only the contact creator can update **Request Body:** ```typescript { name?: string, position?: string, phone?: string, mobile?: string, email?: string, isPrimary?: boolean, isPublic?: boolean, notes?: string } ``` **Response:** ```typescript { success: true, data: Contact, message: "Contact updated successfully" } ``` **Example:** ```typescript const updates = { isPublic: true, notes: "Shared with team", }; const response = await apiClient( `/contacts/${contactId}`, { method: "PUT", body: JSON.stringify(updates), }, ); ``` --- ### 4. Share Contact (Make Public) Make a contact visible to all users in the branch. **Endpoint:** `POST /contacts/:contactId/share` **Path Parameters:** - `contactId` (required) - Contact ID **Rules:** - Only the contact creator can share **Response:** ```typescript { success: true, data: Contact, message: "Contact shared successfully" } ``` **Example:** ```typescript const response = await apiClient( `/contacts/${contactId}/share`, { method: "POST", }, ); ``` --- ### 5. Unshare Contact (Make Private) Make a contact private (visible only to creator). **Endpoint:** `POST /contacts/:contactId/unshare` **Path Parameters:** - `contactId` (required) - Contact ID **Rules:** - Only the contact creator can unshare **Response:** ```typescript { success: true, data: Contact, message: "Contact unshared successfully" } ``` **Example:** ```typescript const response = await apiClient( `/contacts/${contactId}/unshare`, { method: "POST", }, ); ``` --- ### 6. Delete Contact Delete a contact. **Endpoint:** `DELETE /contacts/:contactId` **Path Parameters:** - `contactId` (required) - Contact ID **Rules:** - Only the contact creator can delete **Response:** ```typescript { success: true, data: Contact, message: "Contact deleted successfully" } ``` **Example:** ```typescript const response = await apiClient( `/contacts/${contactId}`, { method: "DELETE", }, ); ``` --- ## Contact Sharing API ### 1. Share Contact with Specific User Share a contact with a specific user (not public). **Endpoint:** `POST /contacts/:contactId/share-with` **Path Parameters:** - `contactId` (required) - Contact ID **Request Body:** ```typescript { targetUserId: string, // Required - User ID to share with notes?: string // Optional - Notes about the share } ``` **Rules:** - Only the contact creator can share - Cannot share with yourself - Cannot share non-existent contact - Cannot share contact from different branch - Duplicate share prevention **Response:** ```typescript { success: true, data: ContactShare, message: "Contact shared successfully" } ``` **Example:** ```typescript const shareRequest = { targetUserId: "user-456", notes: "Sales lead for Q4 project", }; const response = await apiClient( `/contacts/${contactId}/share-with`, { method: "POST", body: JSON.stringify(shareRequest), }, ); ``` --- ### 2. Unshare Contact from Specific User Remove sharing from a specific user. **Endpoint:** `DELETE /contacts/:contactId/share/:targetUserId` **Path Parameters:** - `contactId` (required) - Contact ID - `targetUserId` (required) - User ID to unshare from **Rules:** - Only the contact creator can unshare **Response:** ```typescript { success: true, data: ContactShare, message: "Contact unshared successfully" } ``` **Example:** ```typescript const response = await apiClient( `/contacts/${contactId}/share/${targetUserId}`, { method: "DELETE", }, ); ``` --- ### 3. Get Contact Shares Get all shares for a contact. **Endpoint:** `GET /contacts/:contactId/shares` **Path Parameters:** - `contactId` (required) - Contact ID **Rules:** - Only the contact creator can view shares **Response:** ```typescript { success: true, data: ContactShare[], count: 3, message: "Found 3 share(s)" } ``` **Example:** ```typescript const response = await apiClient( `/contacts/${contactId}/shares`, ); ``` --- ### 4. Get Contacts Shared With Me Get all contacts that have been shared with the current user. **Endpoint:** `GET /contacts/shared-with-me` **Query Parameters:** - `customerId` (optional) - Filter by customer ID **Response:** ```typescript { success: true, data: Contact[], count: 5, message: "Found 5 contact(s) shared with you" } ``` **Example:** ```typescript // Get all contacts shared with me const response1 = await apiClient( "/contacts/shared-with-me", ); // Filter by customer const response2 = await apiClient( `/contacts/shared-with-me?customerId=${customerId}`, ); ``` --- ## Type Definitions All API types are exported from `@/types/api`. ### Import Example: ```typescript import type { Customer, Contact, ContactShare, CustomerListResponse, CreateCustomerRequest, UpdateContactRequest, ApiResponse, } from "@/types/api"; ``` ### Available Types: - **Customer Types:** `Customer`, `CreateCustomerRequest`, `UpdateCustomerRequest` - **Contact Types:** `Contact`, `CreateContactRequest`, `UpdateContactRequest` - **Share Types:** `ContactShare`, `ShareContactRequest` - **Response Types:** `SuccessResponse`, `ErrorResponse`, `ApiResponse` - **List Responses:** `CustomerListResponse`, `ContactListResponse`, `ContactShareListResponse` - **Single Item Responses:** `CustomerResponse`, `ContactResponse`, `ContactShareResponse` - **Operation Responses:** `CreateCustomerResponse`, `UpdateCustomerResponse`, etc. --- ## Usage Examples ### Example 1: Fetch and Display Customers ```typescript "use client"; import { useEffect, useState } from "react"; import { apiClient } from "@/lib/api-client"; import type { CustomerListResponse, Customer } from "@/types/api"; export default function CustomerList() { const [customers, setCustomers] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { async function fetchCustomers() { try { const response = await apiClient("/customers?status=active"); if (response.success) { setCustomers(response.data); } else { setError(response.error); } } catch (err) { setError("Failed to fetch customers"); } finally { setLoading(false); } } fetchCustomers(); }, []); if (loading) return
Loading...
; if (error) return
Error: {error}
; return (
    {customers.map(customer => (
  • {customer.name} - {customer.company}
  • ))}
); } ``` --- ### Example 2: Create New Customer with Form ```typescript "use client"; import { useState } from "react"; import { apiClient } from "@/lib/api-client"; import type { CreateCustomerRequest, CreateCustomerResponse } from "@/types/api"; export default function CreateCustomerForm() { const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [success, setSuccess] = useState(false); async function handleSubmit(e: React.FormEvent) { e.preventDefault(); setLoading(true); setError(null); setSuccess(false); const formData = new FormData(e.currentTarget); const request: CreateCustomerRequest = { name: formData.get("name") as string, email: formData.get("email") as string, phone: formData.get("phone") as string, company: formData.get("company") as string, address: formData.get("address") as string, customerStatus: formData.get("status") as string || undefined }; try { const response = await apiClient("/customers", { method: "POST", body: JSON.stringify(request) }); if (response.success) { setSuccess(true); e.currentTarget.reset(); } else { setError(response.error); } } catch (err) { setError("Failed to create customer"); } finally { setLoading(false); } } return (
{error &&
{error}
} {success &&
Customer created successfully!
}
); } ``` --- ### Example 3: Share Contact with User ```typescript "use client"; import { useState } from "react"; import { apiClient } from "@/lib/api-client"; import type { ShareContactWithUserResponse } from "@/types/api"; interface ShareContactModalProps { contactId: string; onClose: () => void; } export default function ShareContactModal({ contactId, onClose }: ShareContactModalProps) { const [targetUserId, setTargetUserId] = useState(""); const [notes, setNotes] = useState(""); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); async function handleShare() { if (!targetUserId) { setError("Please select a user"); return; } setLoading(true); setError(null); try { const response = await apiClient( `/contacts/${contactId}/share-with`, { method: "POST", body: JSON.stringify({ targetUserId, notes }) } ); if (response.success) { onClose(); // Close modal on success } else { setError(response.error); } } catch (err) { setError("Failed to share contact"); } finally { setLoading(false); } } return (

Share Contact

setTargetUserId(e.target.value)} />