16 KiB
API Documentation
Profitra API Documentation
Base Information
Base URL: https://api.profitra.id/v1
Development URL: http://localhost:8000/v1
Protocol: HTTPS (Production), HTTP (Development)
Content Type: application/json
Authentication: JWT Bearer Token
API Structure
Endpoints Organization
/v1
├── /auth # Authentication
├── /users # User management
├── /tenants # Tenant management
├── /plans # Subscription plans
├── /subscriptions # Subscription management
├── /business-types # Business types
├── /products # Product management (tenant)
├── /categories # Product categories (tenant)
├── /customers # Customer management (tenant)
├── /sales # Sales transactions (tenant)
├── /inventory # Inventory management (tenant)
├── /reports # Reports (tenant)
└── /settings # Settings (tenant)
Authentication
Register
Endpoint: POST /auth/register
Description: Create new user account
Request Body:
{
"full_name": "John Doe",
"email": "john@example.com",
"username": "johndoe",
"password": "securepass123",
"phone": "081234567890",
"timezone": "Asia/Jakarta"
}
Validation Rules:
full_name: required, max 200 charsemail: required, valid email, unique, max 100 charsusername: required, alphanumeric, unique, 3-20 charspassword: required, min 8 charsphone: optional, valid phone numbertimezone: optional, default "Asia/Jakarta"
Success Response (201):
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "john@example.com",
"username": "johndoe",
"tenant_id": null,
"status": "INACTIVE",
"created_at": "2026-07-20T08:00:00Z"
}
Error Response (409):
{
"message": "Email atau username sudah terdaftar."
}
Error Response (400):
{
"message": "Validation errors in your request",
"errors": [
{
"field": "email",
"message": "Invalid email format",
"code": 1001
}
]
}
Login
Endpoint: POST /auth/login
Description: Authenticate user and get JWT token
Request Body:
{
"login": "johndoe",
"password": "securepass123"
}
Validation Rules:
login: required (username or email)password: required
Success Response (200):
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"user": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "john@example.com",
"username": "johndoe",
"tenant_id": "660e8400-e29b-41d4-a716-446655440000",
"status": "ACTIVE",
"created_at": "2026-07-20T08:00:00Z",
"profile": {
"full_name": "John Doe",
"phone": "081234567890",
"timezone": "Asia/Jakarta"
}
}
}
Error Response (401):
{
"message": "Kredensial autentikasi tidak ada atau tidak valid."
}
Error Response (401) - Email not verified:
{
"message": "Email belum diverifikasi. Silakan verifikasi email terlebih dahulu."
}
Verify Email
Endpoint: POST /auth/verify-email
Description: Verify user email with token
Request Body:
{
"token": "verification-token-here"
}
Success Response (200):
{
"message": "Email berhasil diverifikasi",
"user": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "john@example.com",
"status": "ACTIVE",
"email_verified_at": "2026-07-20T08:30:00Z"
}
}
Error Response (400):
{
"message": "Token tidak valid atau sudah kadaluarsa"
}
Resend Verification Email
Endpoint: POST /auth/resend-verification
Description: Resend verification email
Request Body:
{
"email": "john@example.com"
}
Success Response (200):
{
"message": "Email verifikasi telah dikirim ulang"
}
Rate Limit: 3 requests per hour per email
Forgot Password
Endpoint: POST /auth/forgot-password
Description: Request password reset
Request Body:
{
"email": "john@example.com"
}
Success Response (200):
{
"message": "Link reset password telah dikirim ke email Anda"
}
Reset Password
Endpoint: POST /auth/reset-password
Description: Reset password with token
Request Body:
{
"token": "reset-token-here",
"password": "newpassword123",
"password_confirmation": "newpassword123"
}
Success Response (200):
{
"message": "Password berhasil diubah"
}
Logout
Endpoint: POST /auth/logout
Description: Logout user (invalidate token if using refresh token)
Headers:
Authorization: Bearer {token}
Success Response (200):
{
"message": "Logout berhasil"
}
Get Current User
Endpoint: GET /auth/me
Description: Get current authenticated user info
Headers:
Authorization: Bearer {token}
Success Response (200):
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "john@example.com",
"username": "johndoe",
"tenant_id": "660e8400-e29b-41d4-a716-446655440000",
"status": "ACTIVE",
"profile": {
"full_name": "John Doe",
"phone": "081234567890",
"timezone": "Asia/Jakarta"
},
"tenant": {
"id": "660e8400-e29b-41d4-a716-446655440000",
"name": "Toko Maju Jaya",
"slug": "toko-maju-jaya",
"business_type": "RETAIL"
}
}
Business Types
List Business Types
Endpoint: GET /business-types
Description: Get all available business types
Success Response (200):
[
{
"id": "770e8400-e29b-41d4-a716-446655440000",
"code": "RETAIL",
"name": "Retail",
"is_active": true,
"created_at": "2026-01-01T00:00:00Z"
},
{
"id": "880e8400-e29b-41d4-a716-446655440000",
"code": "FNB",
"name": "Food & Beverage",
"is_active": true,
"created_at": "2026-01-01T00:00:00Z"
}
]
Get Business Type
Endpoint: GET /business-types/{id}
Description: Get business type by ID
Success Response (200):
{
"id": "770e8400-e29b-41d4-a716-446655440000",
"code": "RETAIL",
"name": "Retail",
"is_active": true,
"created_at": "2026-01-01T00:00:00Z"
}
Error Response (404):
{
"message": "Business type not found"
}
Subscription Plans
List Plans
Endpoint: GET /plans
Description: Get all available subscription plans
Success Response (200):
[
{
"id": "990e8400-e29b-41d4-a716-446655440000",
"code": "FREE",
"name": "Free Plan",
"description": "30-day trial with basic features",
"price": 0,
"limits": {
"products": 100,
"customers": 100,
"transactions_per_month": 500,
"users": 1,
"locations": 1
},
"is_active": true,
"created_at": "2026-01-01T00:00:00Z"
},
{
"id": "aa0e8400-e29b-41d4-a716-446655440000",
"code": "BASIC",
"name": "Basic Plan",
"description": "For small to medium business",
"price": 99000,
"limits": {
"products": 5000,
"customers": -1,
"transactions_per_month": 5000,
"users": 5,
"locations": 3
},
"is_active": true,
"created_at": "2026-01-01T00:00:00Z"
},
{
"id": "bb0e8400-e29b-41d4-a716-446655440000",
"code": "PRO",
"name": "Pro Plan",
"description": "For medium to large business",
"price": 299000,
"limits": {
"products": -1,
"customers": -1,
"transactions_per_month": -1,
"users": -1,
"locations": -1
},
"is_active": true,
"created_at": "2026-01-01T00:00:00Z"
}
]
Note: -1 means unlimited
Get Plan
Endpoint: GET /plans/{id}
Description: Get plan by ID
Success Response (200):
{
"id": "990e8400-e29b-41d4-a716-446655440000",
"code": "FREE",
"name": "Free Plan",
"description": "30-day trial with basic features",
"price": 0,
"limits": {
"products": 100,
"customers": 100,
"transactions_per_month": 500,
"users": 1,
"locations": 1
},
"is_active": true
}
Tenants
Create Tenant (Onboarding)
Endpoint: POST /tenants
Description: Create new tenant (business onboarding)
Headers:
Authorization: Bearer {token}
Request Body:
{
"name": "Toko Maju Jaya",
"business_type_id": "770e8400-e29b-41d4-a716-446655440000",
"phone": "0215551234",
"email": "info@majujaya.com",
"address": "Jl. Sudirman No. 123",
"city": "Jakarta Pusat",
"province": "DKI Jakarta",
"postal_code": "10110",
"plan_id": "990e8400-e29b-41d4-a716-446655440000",
"settings": {
"currency": "IDR",
"timezone": "Asia/Jakarta",
"tax_rate": 11,
"fiscal_year_start": 1
}
}
Success Response (201):
{
"id": "660e8400-e29b-41d4-a716-446655440000",
"name": "Toko Maju Jaya",
"slug": "toko-maju-jaya",
"business_type_id": "770e8400-e29b-41d4-a716-446655440000",
"phone": "0215551234",
"email": "info@majujaya.com",
"address": "Jl. Sudirman No. 123",
"status": "ACTIVE",
"created_at": "2026-07-20T08:00:00Z",
"subscription": {
"id": "cc0e8400-e29b-41d4-a716-446655440000",
"plan": "FREE",
"status": "TRIAL",
"started_at": "2026-07-20T08:00:00Z",
"ended_at": "2026-08-19T23:59:59Z"
}
}
Process:
- Validate request data
- Generate unique slug
- Create tenant record in central DB
- Create tenant database
- Run migrations on tenant DB
- Seed default data
- Create subscription
- Link user to tenant
- Assign OWNER role to user in tenant DB
- Send welcome email
Get Tenant
Endpoint: GET /tenants/{id}
Description: Get tenant details
Headers:
Authorization: Bearer {token}
Success Response (200):
{
"id": "660e8400-e29b-41d4-a716-446655440000",
"name": "Toko Maju Jaya",
"slug": "toko-maju-jaya",
"business_type": {
"code": "RETAIL",
"name": "Retail"
},
"phone": "0215551234",
"email": "info@majujaya.com",
"address": "Jl. Sudirman No. 123",
"status": "ACTIVE",
"created_at": "2026-07-20T08:00:00Z",
"subscription": {
"plan": "FREE",
"status": "TRIAL",
"expires_at": "2026-08-19T23:59:59Z"
}
}
Update Tenant
Endpoint: PUT /tenants/{id}
Description: Update tenant information
Headers:
Authorization: Bearer {token}
Permission Required: tenants.edit or OWNER role
Request Body:
{
"name": "Toko Maju Jaya Sejahtera",
"phone": "0215551235",
"email": "info@majujaya.co.id",
"address": "Jl. Sudirman No. 125"
}
Success Response (200):
{
"id": "660e8400-e29b-41d4-a716-446655440000",
"name": "Toko Maju Jaya Sejahtera",
"slug": "toko-maju-jaya",
"phone": "0215551235",
"email": "info@majujaya.co.id",
"address": "Jl. Sudirman No. 125",
"updated_at": "2026-07-20T09:00:00Z"
}
Products (Tenant-specific)
Note: All product endpoints require tenant context from JWT token
List Products
Endpoint: GET /products
Description: Get all products for current tenant
Headers:
Authorization: Bearer {token}
Query Parameters:
page: integer, default 1limit: integer, default 20, max 100search: string, search in name/skucategory_id: uuid, filter by categorystatus: string, filter by status (active/inactive)sort: string, sort field (name, price, stock)order: string, sort order (asc/desc)
Success Response (200):
{
"data": [
{
"id": "dd0e8400-e29b-41d4-a716-446655440000",
"sku": "PRD-001",
"name": "Product A",
"description": "Description of product A",
"category_id": "ee0e8400-e29b-41d4-a716-446655440000",
"category_name": "Electronics",
"unit": "pcs",
"purchase_price": 40000,
"selling_price": 50000,
"stock": 100,
"min_stock": 10,
"status": "active",
"image": "https://cdn.profitra.id/products/product-a.jpg",
"created_at": "2026-07-20T08:00:00Z",
"updated_at": "2026-07-20T08:00:00Z"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 50,
"total_pages": 3
}
}
Get Product
Endpoint: GET /products/{id}
Description: Get product by ID
Headers:
Authorization: Bearer {token}
Success Response (200):
{
"id": "dd0e8400-e29b-41d4-a716-446655440000",
"sku": "PRD-001",
"barcode": "1234567890123",
"name": "Product A",
"description": "Description of product A",
"category_id": "ee0e8400-e29b-41d4-a716-446655440000",
"category": {
"id": "ee0e8400-e29b-41d4-a716-446655440000",
"name": "Electronics"
},
"unit": "pcs",
"purchase_price": 40000,
"selling_price": 50000,
"stock": 100,
"min_stock": 10,
"max_stock": 500,
"location": "Rak A-1",
"status": "active",
"images": [
"https://cdn.profitra.id/products/product-a-1.jpg",
"https://cdn.profitra.id/products/product-a-2.jpg"
],
"variants": [
{
"id": "ff0e8400-e29b-41d4-a716-446655440000",
"name": "Size M",
"sku": "PRD-001-M",
"price": 50000,
"stock": 50
}
],
"created_at": "2026-07-20T08:00:00Z",
"updated_at": "2026-07-20T08:00:00Z"
}
Create Product
Endpoint: POST /products
Description: Create new product
Headers:
Authorization: Bearer {token}
Permission Required: products.create
Request Body:
{
"sku": "PRD-002",
"barcode": "1234567890124",
"name": "Product B",
"description": "Description of product B",
"category_id": "ee0e8400-e29b-41d4-a716-446655440000",
"unit": "pcs",
"purchase_price": 60000,
"selling_price": 75000,
"stock": 50,
"min_stock": 5,
"max_stock": 200,
"location": "Rak B-1",
"status": "active"
}
Success Response (201):
{
"id": "110e8400-e29b-41d4-a716-446655440000",
"sku": "PRD-002",
"name": "Product B",
"selling_price": 75000,
"stock": 50,
"created_at": "2026-07-20T09:00:00Z"
}
Error Response (403) - Limit Reached:
{
"message": "Product limit reached (100). Please upgrade your plan."
}
Update Product
Endpoint: PUT /products/{id}
Description: Update product
Headers:
Authorization: Bearer {token}
Permission Required: products.edit
Request Body:
{
"name": "Product B Updated",
"selling_price": 80000,
"stock": 45
}
Success Response (200):
{
"id": "110e8400-e29b-41d4-a716-446655440000",
"name": "Product B Updated",
"selling_price": 80000,
"stock": 45,
"updated_at": "2026-07-20T10:00:00Z"
}
Delete Product
Endpoint: DELETE /products/{id}
Description: Soft delete product
Headers:
Authorization: Bearer {token}
Permission Required: products.delete
Success Response (204):
No Content
Response Format Standards
Success Response
Single Resource:
{
"id": "uuid",
"field": "value"
}
List Resources:
{
"data": [...],
"pagination": {
"page": 1,
"limit": 20,
"total": 100,
"total_pages": 5
}
}
Operation Success:
{
"message": "Operation completed successfully"
}
Error Response
400 Bad Request:
{
"message": "Validation errors",
"errors": [
{
"field": "email",
"message": "Invalid email format",
"code": 1001
}
]
}
401 Unauthorized:
{
"message": "Authentication credentials were missing or incorrect"
}
403 Forbidden:
{
"message": "You don't have permission to access this resource"
}
404 Not Found:
{
"message": "Resource not found"
}
409 Conflict:
{
"message": "Resource already exists"
}
429 Too Many Requests:
{
"message": "Rate limit exceeded. Try again later."
}
500 Internal Server Error:
{
"message": "An unexpected error occurred"
}
Rate Limiting
Default Limits:
- Anonymous: 100 requests/hour
- Authenticated: 1000 requests/hour
- API Key (Pro plan): 10000 requests/hour
Headers:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1626782400
Versioning
API menggunakan URL versioning: /v1, /v2, etc.
Current version: v1
Last Updated: 2026-07-20