API Package
The API package provides a complete REST API with secure authentication, rate limiting, webhooks, and OpenAPI documentation.
Installation
bash
composer require host-uk/core-apiQuick Start
php
<?php
namespace Mod\Blog;
use Core\Events\ApiRoutesRegistering;
class Boot
{
public static array $listens = [
ApiRoutesRegistering::class => 'onApiRoutes',
];
public function onApiRoutes(ApiRoutesRegistering $event): void
{
$event->routes(function () {
Route::get('/posts', [Api\PostController::class, 'index']);
Route::post('/posts', [Api\PostController::class, 'store']);
Route::get('/posts/{id}', [Api\PostController::class, 'show']);
});
}
}Key Features
Authentication & Security
- API Keys - Secure API key management with bcrypt hashing
- Scopes - Fine-grained permission system
- Rate Limiting - Tier-based rate limits with Redis backend
- Key Rotation - Secure key rotation with grace periods
Webhooks
- Webhook Endpoints - Event-driven notifications
- Signatures - HMAC-SHA256 signature verification
- Delivery Tracking - Retry logic and delivery history
Documentation
- OpenAPI Spec - Auto-generated OpenAPI 3.0 documentation
- Interactive Docs - Swagger UI, Scalar, and ReDoc interfaces
- Code Examples - Multi-language code snippets
Monitoring
- Usage Analytics - Track API usage and quota
- Usage Alerts - Automated high-usage notifications
- Request Logging - Comprehensive request/response logging
Authentication
Creating API Keys
php
use Mod\Api\Models\ApiKey;
$apiKey = ApiKey::create([
'name' => 'Mobile App',
'workspace_id' => $workspace->id,
'scopes' => ['posts:read', 'posts:write'],
'rate_limit_tier' => 'pro',
]);
// Get plaintext key (only shown once!)
$plaintext = $apiKey->plaintext_key; // sk_live_abc123...Using API Keys
bash
curl -H "Authorization: Bearer sk_live_abc123..." \
https://api.example.com/v1/postsLearn more about Authentication →
Rate Limiting
Tier-based rate limits with automatic enforcement:
php
// config/api.php
'rate_limits' => [
'free' => ['requests' => 1000, 'per' => 'hour'],
'pro' => ['requests' => 10000, 'per' => 'hour'],
'business' => ['requests' => 50000, 'per' => 'hour'],
'enterprise' => ['requests' => null], // Unlimited
],Rate limit headers included in every response:
X-RateLimit-Limit: 10000
X-RateLimit-Remaining: 9847
X-RateLimit-Reset: 1640995200Learn more about Rate Limiting →
Webhooks
Creating Webhooks
php
use Mod\Api\Models\WebhookEndpoint;
$webhook = WebhookEndpoint::create([
'url' => 'https://your-app.com/webhooks',
'events' => ['post.created', 'post.updated'],
'secret' => 'whsec_abc123...',
'workspace_id' => $workspace->id,
]);Dispatching Events
php
use Mod\Api\Services\WebhookService;
$service = app(WebhookService::class);
$service->dispatch('post.created', [
'id' => $post->id,
'title' => $post->title,
'url' => route('posts.show', $post),
]);Verifying Signatures
php
use Mod\Api\Services\WebhookSignature;
$signature = WebhookSignature::verify(
payload: $request->getContent(),
signature: $request->header('X-Webhook-Signature'),
secret: $webhook->secret
);
if (!$signature) {
abort(401, 'Invalid signature');
}OpenAPI Documentation
Auto-generate OpenAPI documentation with attributes:
php
use Mod\Api\Documentation\Attributes\ApiTag;
use Mod\Api\Documentation\Attributes\ApiParameter;
use Mod\Api\Documentation\Attributes\ApiResponse;
#[ApiTag('Posts')]
class PostController extends Controller
{
#[ApiParameter(name: 'page', in: 'query', type: 'integer')]
#[ApiParameter(name: 'per_page', in: 'query', type: 'integer')]
#[ApiResponse(status: 200, description: 'List of posts')]
public function index(Request $request)
{
return PostResource::collection(
Post::paginate($request->input('per_page', 15))
);
}
}View documentation at:
/api/docs- Swagger UI/api/docs/scalar- Scalar interface/api/docs/redoc- ReDoc interface
Learn more about Documentation →
API Resources
Transform models to JSON:
php
<?php
namespace Mod\Blog\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class PostResource extends JsonResource
{
public function toArray($request): array
{
return [
'id' => $this->id,
'title' => $this->title,
'slug' => $this->slug,
'excerpt' => $this->excerpt,
'content' => $this->when(
$request->user()->tokenCan('posts:read-content'),
$this->content
),
'status' => $this->status,
'published_at' => $this->published_at?->toIso8601String(),
'created_at' => $this->created_at->toIso8601String(),
'updated_at' => $this->updated_at->toIso8601String(),
];
}
}Configuration
php
// config/api.php
return [
'prefix' => 'api/v1',
'middleware' => ['api'],
'rate_limits' => [
'free' => ['requests' => 1000, 'per' => 'hour'],
'pro' => ['requests' => 10000, 'per' => 'hour'],
'business' => ['requests' => 50000, 'per' => 'hour'],
'enterprise' => ['requests' => null],
],
'api_keys' => [
'hash_algo' => 'bcrypt',
'prefix' => 'sk',
'length' => 32,
],
'webhooks' => [
'max_retries' => 3,
'retry_delay' => 60, // seconds
'signature_algo' => 'sha256',
],
'documentation' => [
'enabled' => true,
'middleware' => ['web', 'auth'],
'title' => 'API Documentation',
],
];Best Practices
1. Use API Resources
php
// ✅ Good - API resource
return PostResource::collection($posts);
// ❌ Bad - raw model data
return $posts->toArray();2. Implement Scopes
php
// ✅ Good - scope protection
Route::middleware('scope:posts:write')
->post('/posts', [PostController::class, 'store']);3. Verify Webhook Signatures
php
// ✅ Good - verify signature
if (!WebhookSignature::verify($payload, $signature, $secret)) {
abort(401);
}4. Use Rate Limit Middleware
php
// ✅ Good - rate limited
Route::middleware('api.rate-limit')
->group(function () {
// API routes
});Testing
php
<?php
namespace Tests\Feature\Api;
use Tests\TestCase;
use Mod\Api\Models\ApiKey;
class PostApiTest extends TestCase
{
public function test_lists_posts(): void
{
$apiKey = ApiKey::factory()->create([
'scopes' => ['posts:read'],
]);
$response = $this->withHeaders([
'Authorization' => "Bearer {$apiKey->plaintext_key}",
])->getJson('/api/v1/posts');
$response->assertOk()
->assertJsonStructure([
'data' => [
'*' => ['id', 'title', 'slug'],
],
]);
}
}