The BDM Universal API uses a layered configuration approach with global settings, module-specific configurations, and environment variables.
.env file for sensitive data and deployment settings
System-wide API settings in config/bdm_api.php
Per-module settings in config/modules/
Configure these settings in your .env file:
# Application APP_NAME=BDM APP_ENV=production APP_DEBUG=false APP_URL=https://yourdomain.com # Timezone APP_TIMEZONE=Africa/Johannesburg
Note: Set APP_DEBUG=false in production to hide error details from API responses.
# Database DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=bdm_production DB_USERNAME=bdm_user DB_PASSWORD=your_secure_password
# Cache (Redis recommended for production) CACHE_DRIVER=redis REDIS_HOST=127.0.0.1 REDIS_PASSWORD=null REDIS_PORT=6379 # Queue (For webhooks, exports) QUEUE_CONNECTION=redis
⚠️ Important: Redis is strongly recommended for production to handle rate limiting and caching efficiently.
# Sanctum SANCTUM_STATEFUL_DOMAINS=yourdomain.com,api.yourdomain.com SESSION_DOMAIN=.yourdomain.com SESSION_SECURE_COOKIE=true
# API Rate Limits BDM_API_RATE_LIMIT_PER_MINUTE=100 BDM_API_RATE_LIMIT_PER_HOUR=5000 BDM_GUEST_RATE_LIMIT_PER_MINUTE=20 BDM_GUEST_RATE_LIMIT_PER_HOUR=100
# Logging BDM_API_LOG_REQUESTS=true BDM_API_LOG_REQUEST_BODY=true BDM_API_LOG_RESPONSE_BODY=true BDM_API_LOG_RETENTION_DAYS=90 # Performance BDM_API_SLOW_QUERY_THRESHOLD=1000
# Mail
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=your_username
MAIL_PASSWORD=your_password
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=noreply@yourdomain.com
MAIL_FROM_NAME="${APP_NAME}"
Edit config/bdm_api.php for system-wide API settings:
<?php
return [
/*
|--------------------------------------------------------------------------
| API Version
|--------------------------------------------------------------------------
*/
'version' => env('BDM_API_VERSION', 'v1'),
/*
|--------------------------------------------------------------------------
| Pagination
|--------------------------------------------------------------------------
*/
'pagination' => [
'default_per_page' => 15,
'max_per_page' => 100,
],
/*
|--------------------------------------------------------------------------
| Rate Limiting
|--------------------------------------------------------------------------
*/
'rate_limits' => [
'authenticated' => [
'per_minute' => env('BDM_API_RATE_LIMIT_PER_MINUTE', 100),
'per_hour' => env('BDM_API_RATE_LIMIT_PER_HOUR', 5000),
],
'api_token' => [
'per_minute' => 200,
'per_hour' => 10000,
],
'guest' => [
'per_minute' => env('BDM_GUEST_RATE_LIMIT_PER_MINUTE', 20),
'per_hour' => env('BDM_GUEST_RATE_LIMIT_PER_HOUR', 100),
],
'anonymous' => [
'per_minute' => 10,
'per_hour' => 50,
],
],
/*
|--------------------------------------------------------------------------
| Request Logging
|--------------------------------------------------------------------------
*/
'logging' => [
'enabled' => env('BDM_API_LOG_REQUESTS', true),
'log_request_body' => env('BDM_API_LOG_REQUEST_BODY', true),
'log_response_body' => env('BDM_API_LOG_RESPONSE_BODY', true),
'max_body_length' => 10000,
'retention_days' => env('BDM_API_LOG_RETENTION_DAYS', 90),
'exclude_paths' => [
'/api/health',
'/api/status',
],
],
/*
|--------------------------------------------------------------------------
| CORS Configuration
|--------------------------------------------------------------------------
*/
'cors' => [
'allowed_origins' => ['*'],
'allowed_methods' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
'allowed_headers' => ['*'],
'exposed_headers' => [
'X-RateLimit-Limit',
'X-RateLimit-Remaining',
'X-RateLimit-Reset',
],
'max_age' => 86400,
],
/*
|--------------------------------------------------------------------------
| Authentication
|--------------------------------------------------------------------------
*/
'authentication' => [
'token_prefix' => 'bdm_',
'token_length' => 80,
'guest_token_length' => 128,
],
/*
|--------------------------------------------------------------------------
| Caching
|--------------------------------------------------------------------------
*/
'cache' => [
'permission_ttl' => 900, // 15 minutes
'user_module_list_ttl' => 3600, // 1 hour
],
/*
|--------------------------------------------------------------------------
| Performance
|--------------------------------------------------------------------------
*/
'performance' => [
'slow_query_threshold' => env('BDM_API_SLOW_QUERY_THRESHOLD', 1000),
'enable_query_caching' => true,
],
];
Each module requires two configuration files in config/modules/:
config/modules/bdm_accounting_permissions.php
<?php
return [
/*
|--------------------------------------------------------------------------
| Available Roles
|--------------------------------------------------------------------------
*/
'roles' => [
'owner' => [
'label' => 'Owner',
'permissions' => ['*'],
],
'admin' => [
'label' => 'Administrator',
'permissions' => [
'invoices.view',
'invoices.create',
'invoices.edit',
'invoices.delete',
'invoices.send',
'expenses.view',
'expenses.create',
'expenses.edit',
'expenses.delete',
'reports.view',
'reports.export',
],
],
'manager' => [
'label' => 'Manager',
'permissions' => [
'invoices.view',
'invoices.create',
'invoices.edit',
'invoices.send',
'expenses.view',
'expenses.create',
'expenses.edit',
'reports.view',
],
],
'staff' => [
'label' => 'Staff Member',
'permissions' => [
'invoices.view',
'invoices.create',
'expenses.view',
'expenses.create',
],
],
'viewer' => [
'label' => 'Read-Only Viewer',
'permissions' => [
'invoices.view',
'expenses.view',
'reports.view',
],
],
],
/*
|--------------------------------------------------------------------------
| Guest Access Defaults
|--------------------------------------------------------------------------
*/
'guest_access' => [
'invoice' => [
'default_permissions' => ['view', 'download', 'pay'],
'default_expiry_days' => 30,
'requires_password' => false,
'requires_verification' => false,
],
'expense' => [
'default_permissions' => ['view'],
'default_expiry_days' => 7,
'requires_password' => true,
'requires_verification' => false,
],
],
];
config/modules/bdm_accounting_api.php
<?php
return [
/*
|--------------------------------------------------------------------------
| Searchable Fields
|--------------------------------------------------------------------------
*/
'searchable_fields' => [
'invoices' => ['number', 'customer_name', 'customer_email'],
'expenses' => ['description', 'vendor', 'category'],
],
/*
|--------------------------------------------------------------------------
| Sortable Fields
|--------------------------------------------------------------------------
*/
'sortable_fields' => [
'invoices' => ['number', 'total', 'due_date', 'created_at'],
'expenses' => ['amount', 'date', 'created_at'],
],
/*
|--------------------------------------------------------------------------
| Filterable Fields
|--------------------------------------------------------------------------
*/
'filterable_fields' => [
'invoices' => ['status', 'customer_id'],
'expenses' => ['category', 'payment_method'],
],
/*
|--------------------------------------------------------------------------
| Pagination
|--------------------------------------------------------------------------
*/
'default_per_page' => 15,
'max_per_page' => 100,
/*
|--------------------------------------------------------------------------
| Webhook Events
|--------------------------------------------------------------------------
*/
'webhook_events' => [
'invoice.created' => 'Invoice Created',
'invoice.updated' => 'Invoice Updated',
'invoice.sent' => 'Invoice Sent',
'invoice.paid' => 'Invoice Paid',
'invoice.overdue' => 'Invoice Overdue',
'expense.created' => 'Expense Created',
'expense.approved' => 'Expense Approved',
],
];
Edit config/sanctum.php:
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', 'localhost')),
'expiration' => null, // Never expire (or set in minutes)
'middleware' => [
'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class,
],
# config/bdm_api.php
'authentication' => [
// Token prefix for API tokens
'token_prefix' => 'bdm_',
// Length of API tokens
'token_length' => 80,
// Length of guest access tokens
'guest_token_length' => 128,
// Token expiration (null = never)
'token_expiration' => null,
];
| Type | Per Minute | Per Hour | Config |
|---|---|---|---|
| Authenticated User | 100 | 5,000 | BDM_API_RATE_LIMIT_* |
| API Token | 200 | 10,000 | Per token config |
| Guest Access | 20 | 100 | BDM_GUEST_RATE_LIMIT_* |
| Anonymous | 10 | 50 | Fixed in config |
# .env - Adjust these values # Development (higher limits) BDM_API_RATE_LIMIT_PER_MINUTE=200 BDM_API_RATE_LIMIT_PER_HOUR=10000 # Production (standard) BDM_API_RATE_LIMIT_PER_MINUTE=100 BDM_API_RATE_LIMIT_PER_HOUR=5000 # Guest Access (stricter) BDM_GUEST_RATE_LIMIT_PER_MINUTE=20 BDM_GUEST_RATE_LIMIT_PER_HOUR=100
Set custom limits when creating API tokens:
POST /api/tokens
{
"name": "High Volume Integration",
"scopes": ["accounting.*"],
"rate_limit_per_minute": 500, // Custom limit
"rate_limit_per_hour": 20000 // Custom limit
}
Edit config/cors.php:
'paths' => ['api/*'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'], // Allow all in development
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [
'X-RateLimit-Limit',
'X-RateLimit-Remaining',
'X-RateLimit-Reset',
],
'max_age' => 0,
'supports_credentials' => true,
'allowed_origins' => [
'https://yourdomain.com',
'https://app.yourdomain.com',
'https://admin.yourdomain.com',
],
'allowed_origins_patterns' => [
'/^https:\/\/.*\.yourdomain\.com$/', // Allow all subdomains
],
'max_age' => 86400, // Cache preflight for 24 hours
Never use 'allowed_origins' => ['*'] in production. Always specify exact domains or use patterns.
# config/bdm_guest_access.php
return [
'token_length' => 128,
'password' => [
'min_length' => 6,
'require_special_chars' => false,
],
'verification_code' => [
'length' => 6,
'expiry_minutes' => 15,
],
'device_tracking' => [
'enabled' => true,
'max_devices' => 3,
],
'ip_restrictions' => [
'enabled' => true,
'max_ips' => 5,
],
];
# config/hashing.php
'bcrypt' => [
'rounds' => env('BCRYPT_ROUNDS', 12), // Higher = more secure but slower
],
# .env SESSION_DRIVER=redis SESSION_LIFETIME=120 SESSION_SECURE_COOKIE=true # HTTPS only SESSION_HTTP_ONLY=true # Prevent JavaScript access SESSION_SAME_SITE=lax # CSRF protection
# config/bdm_api.php
'logging' => [
'enabled' => true,
// Log request body (careful with sensitive data)
'log_request_body' => env('BDM_API_LOG_REQUEST_BODY', true),
// Log response body
'log_response_body' => env('BDM_API_LOG_RESPONSE_BODY', true),
// Max length of request/response body to log
'max_body_length' => 10000,
// Retention period for logs
'retention_days' => env('BDM_API_LOG_RETENTION_DAYS', 90),
// Exclude specific paths from logging
'exclude_paths' => [
'/api/health',
'/api/status',
'/api/metrics',
],
// Exclude sensitive fields from logging
'exclude_fields' => [
'password',
'password_confirmation',
'credit_card',
'cvv',
],
];
Schedule log cleanup in app/Console/Kernel.php:
protected function schedule(Schedule $schedule)
{
// Clean up old API request logs daily
$schedule->command('bdm:cleanup-logs')
->daily()
->at('02:00');
}
Request logging can consume significant disk space. For high-traffic APIs:
# config/bdm_api.php
'cache' => [
// Cache permission checks for 15 minutes
'permission_ttl' => 900,
// Cache user's module list for 1 hour
'user_module_list_ttl' => 3600,
// Cache API responses (per endpoint config)
'response_cache_enabled' => false,
];
# config/database.php
'mysql' => [
// ... other settings
'strict' => true,
'engine' => 'InnoDB',
// Connection pooling
'pool' => [
'size' => 10,
],
// Query caching
'options' => [
PDO::ATTR_PERSISTENT => true,
],
];
# config/queue.php
'connections' => [
'redis' => [
'driver' => 'redis',
'connection' => 'default',
'queue' => env('REDIS_QUEUE', 'default'),
'retry_after' => 90,
'block_for' => null,
'after_commit' => false,
],
],
# config/bdm_webhooks.php
return [
'retry' => [
'max_attempts' => 3,
'backoff' => [
1 => 60, // 1 minute after first failure
2 => 300, // 5 minutes after second
3 => 1800, // 30 minutes after third
],
],
'timeout' => 30, // Seconds
'signature' => [
'enabled' => true,
'algorithm' => 'sha256',
'header' => 'X-BDM-Signature',
],
'rate_limit' => [
'per_minute' => 100,
],
];
# .env QUEUE_CONNECTION=redis # Webhook-specific queue WEBHOOK_QUEUE=webhooks
Ensure webhook delivery with: