· Admin

Laravel Uygulamanizi AI Agent'lara Uyumlu Hale Getirme

Bir donem API'lerimizi sadece insanlarin kullanacagi frontend uygulamalar icin yaziyorduk. Simdi manzara degisti. API'lerinizi AI agent'lar, chatbot'lar, otomasyon araclari ve hatta baska AI'lar tuketiyor. Peki Laravel uygulamaniz buna hazir mi?

Bu yazida Laravel API'lerinizi AI agent'larin kolay tuketebilecegi, dogru anlayabilecegi ve guvenle kullanbilecegi hale nasil getirecegimizi gorecegiz.

Neden Onemli?

AI agent'lar API'leri insan gibi "tahmin ederek" kullanmaz. Dokumantasyona, tutarli response yapilarina ve tahmin edilebilir URL pattern'lerine ihtiyac duyar. Kaotik bir API, bir insanin "aa suna benziyor" diyerek kullanabilecegi turden, AI icin kabus.

Ornek: ChatGPT Plugins, Claude MCP, LangChain, AutoGPT gibi araclar hep API uzerinden calisiyor. Yarin otegun bir AI sizin API'nizi kesfedip kullanmaya basladiginda, ne kadar "agent-friendly" oldugunuz belirleyici olacak.

1. Tutarli Response Yapisi

Ilk ve en onemli kural: TUM endpoint'leriniz ayni response yapisini dondurmeli.

// app/Http/Responses/ApiResponse.php
namespace App\Http\Responses;

use Illuminate\Http\JsonResponse;

class ApiResponse
{
    public static function success(
        mixed $data = null,
        string $message = 'Success',
        int $status = 200
    ): JsonResponse {
        return response()->json([
            'success' => true,
            'message' => $message,
            'data' => $data,
            'meta' => [
                'timestamp' => now()->toIso8601String(),
                'version' => 'v1',
            ],
        ], $status);
    }

    public static function error(
        string $message = 'Error',
        int $status = 400,
        ?array $errors = null,
        ?string $code = null
    ): JsonResponse {
        return response()->json([
            'success' => false,
            'message' => $message,
            'error_code' => $code,
            'errors' => $errors,
            'meta' => [
                'timestamp' => now()->toIso8601String(),
                'version' => 'v1',
            ],
        ], $status);
    }

    public static function paginated(
        $paginator,
        string $message = 'Success'
    ): JsonResponse {
        return response()->json([
            'success' => true,
            'message' => $message,
            'data' => $paginator->items(),
            'pagination' => [
                'current_page' => $paginator->currentPage(),
                'last_page' => $paginator->lastPage(),
                'per_page' => $paginator->perPage(),
                'total' => $paginator->total(),
                'has_more' => $paginator->hasMorePages(),
            ],
            'meta' => [
                'timestamp' => now()->toIso8601String(),
                'version' => 'v1',
            ],
        ], 200);
    }
}

Neden bu kadar onemli? AI agent'lar response'u parse ederken success field'ina bakip islemin basarili olup olmadigini anlar. data her zaman ayni yerde olur. errors ayrintili hata bilgisi verir. Tahmin etmek zorunda kalmaz.

2. OpenAPI/Swagger Dokumantasyonu

AI agent'larin API'nizi "okuyabilmesi" icin OpenAPI spesifikasyonu sart:

composer require dedoc/scramble
// config/scramble.php
return [
    'info' => [
        'title' => 'ProjeMan API',
        'description' => 'E-ticaret yonetim sistemi API dokumantasyonu',
        'version' => '1.0.0',
    ],
    'servers' => [
        ['url' => env('APP_URL') . '/api/v1'],
    ],
];

Scramble, Laravel route'larinizdan ve Form Request'lerinizden otomatik OpenAPI spec'i uretir. Ama biz bir adim daha ileri gidelim ve PHPDoc ile zenginlestirelim:

// app/Http/Controllers/Api/V1/ProductController.php
namespace App\Http\Controllers\Api\V1;

use App\Http\Requests\ProductIndexRequest;
use App\Http\Resources\ProductResource;
use App\Models\Product;

class ProductController extends Controller
{
    /**
     * Urunleri listele
     *
     * Tum urunleri sayfalanmis olarak listeler.
     * Kategori, fiyat araligi ve stok durumuna gore filtrelenebilir.
     *
     * @queryParam category_id integer Kategori ID'sine gore filtrele. Example: 5
     * @queryParam min_price number Minimum fiyat. Example: 10.00
     * @queryParam max_price number Maksimum fiyat. Example: 500.00
     * @queryParam in_stock boolean Sadece stokta olanlari goster. Example: true
     * @queryParam sort string Siralama alani (name, price, created_at). Example: price
     * @queryParam order string Siralama yonu (asc, desc). Example: asc
     * @queryParam per_page integer Sayfa basina kayit (max: 100). Example: 25
     */
    public function index(ProductIndexRequest $request)
    {
        $products = Product::query()
            ->when($request->category_id, fn ($q, $id) => $q->where('category_id', $id))
            ->when($request->min_price, fn ($q, $min) => $q->where('price', '>=', $min))
            ->when($request->max_price, fn ($q, $max) => $q->where('price', '<=', $max))
            ->when($request->boolean('in_stock'), fn ($q) => $q->where('stock_quantity', '>', 0))
            ->orderBy($request->input('sort', 'created_at'), $request->input('order', 'desc'))
            ->paginate($request->input('per_page', 25));

        return ApiResponse::paginated($products);
    }

    /**
     * Urun detayi
     *
     * Belirtilen ID'ye sahip urunun tum detaylarini,
     * iliskili kategori ve varyant bilgileriyle birlikte dondurur.
     */
    public function show(Product $product)
    {
        $product->load(['category', 'variants', 'images']);

        return ApiResponse::success(
            new ProductResource($product),
            'Urun detayi basariyla getirildi'
        );
    }
}

3. Tutarli Hata Formati

AI agent'lar icin hata mesajlari cok kritik. Agent hatanin ne oldugunu anlayip bir sonraki adimina karar vermeli:

// app/Exceptions/Handler.php (veya bootstrap/app.php)
use Illuminate\Foundation\Configuration\Exceptions;

->withExceptions(function (Exceptions $exceptions) {

    // Validation hatalari
    $exceptions->renderable(function (ValidationException $e, $request) {
        if ($request->expectsJson()) {
            return ApiResponse::error(
                message: 'Validasyon hatasi',
                status: 422,
                errors: $e->errors(),
                code: 'VALIDATION_ERROR'
            );
        }
    });

    // Model bulunamadi
    $exceptions->renderable(function (ModelNotFoundException $e, $request) {
        if ($request->expectsJson()) {
            $model = class_basename($e->getModel());
            return ApiResponse::error(
                message: "{$model} bulunamadi",
                status: 404,
                code: 'RESOURCE_NOT_FOUND'
            );
        }
    });

    // Yetkilendirme hatasi
    $exceptions->renderable(function (AuthorizationException $e, $request) {
        if ($request->expectsJson()) {
            return ApiResponse::error(
                message: 'Bu islemi yapmaya yetkiniz yok',
                status: 403,
                code: 'FORBIDDEN'
            );
        }
    });

    // Rate limit
    $exceptions->renderable(function (ThrottleRequestsException $e, $request) {
        if ($request->expectsJson()) {
            return ApiResponse::error(
                message: 'Cok fazla istek gonderdiniz. Lutfen bekleyin.',
                status: 429,
                code: 'RATE_LIMIT_EXCEEDED',
                errors: [
                    'retry_after' => $e->getHeaders()['Retry-After'] ?? 60,
                ],
            );
        }
    });
})

Neden error_code field'i var? HTTP status code yetmiyor mu? Yetmiyor. AI agent 422 gordugunce "validasyon hatasi" anlayabilir ama VALIDATION_ERROR gordugunce kesin bilir. Ustune errors array'indeki field bazli hatalarla sorunlu alanlari duzeltip tekrar deneyebilir.

4. Tahmin Edilebilir URL Pattern'leri

REST konvansiyonlarina SIKI SIKI bagli kalin:

// routes/api.php

// DOGRU - Tahmin edilebilir
Route::prefix('v1')->group(function () {
    Route::apiResource('products', ProductController::class);
    Route::apiResource('products.reviews', ProductReviewController::class);
    Route::apiResource('categories', CategoryController::class);
    Route::apiResource('orders', OrderController::class);

    // Nested resources mantikli
    Route::get('categories/{category}/products', [CategoryController::class, 'products']);

    // Aksiyonlar icin tutarli pattern
    Route::post('orders/{order}/cancel', [OrderController::class, 'cancel']);
    Route::post('orders/{order}/refund', [OrderController::class, 'refund']);
});

// YANLIS - AI agent bunu tahmin edemez
Route::get('urunler-listesi', ...);
Route::post('siparis-olustur', ...);
Route::get('kategori-urunleri/{id}', ...);

5. API Versiyonlama

AI agent'lar bir kere entegre olduktan sonra API'nin degismesini istemez. Versiyonlama zorunlu:

// routes/api.php
Route::prefix('v1')->group(function () {
    Route::apiResource('products', Api\V1\ProductController::class);
});

Route::prefix('v2')->group(function () {
    Route::apiResource('products', Api\V2\ProductController::class);
});

Header-based versiyonlama da yapabilirsiniz ama URL-based daha acik ve AI-friendly:

// app/Http/Middleware/ApiVersion.php
namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class ApiVersion
{
    public function handle(Request $request, Closure $next, string $version): mixed
    {
        $request->attributes->set('api_version', $version);

        $response = $next($request);

        $response->headers->set('X-API-Version', $version);
        $response->headers->set('X-API-Deprecated', $version === 'v1' ? 'true' : 'false');
        $response->headers->set('X-API-Sunset', $version === 'v1' ? '2027-01-01' : '');

        return $response;
    }
}

X-API-Deprecated ve X-API-Sunset header'lari AI agent'a "bu versiyon yakinda kapanacak, v2'ye gec" sinyali verir.

6. Rate Limiting Stratejisi

AI agent'lar bazen agresif olabilir. Ama cok siki rate limit de isi kirar:

// app/Providers/AppServiceProvider.php
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;

public function boot(): void
{
    // Normal kullanicilar
    RateLimiter::for('api', function (Request $request) {
        return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
    });

    // AI agent'lar icin ayri limit (daha yuksek)
    RateLimiter::for('api-agent', function (Request $request) {
        return Limit::perMinute(200)->by($request->bearerToken());
    });

    // Agir islemler icin ayri limit
    RateLimiter::for('api-heavy', function (Request $request) {
        return Limit::perMinute(10)->by($request->user()?->id ?: $request->ip());
    });
}

Response header'larinda kalan limit bilgisini her zaman dondurur:

X-RateLimit-Limit: 200
X-RateLimit-Remaining: 195
X-RateLimit-Reset: 1709912400

AI agent bu bilgilerle kendini ayarlayabilir.

7. Webhook Destegi

AI agent'lar sadece veri cekmez, olaylara tepki de vermek ister:

// app/Http/Controllers/Api/V1/WebhookController.php
namespace App\Http\Controllers\Api\V1;

class WebhookController extends Controller
{
    /**
     * Webhook endpoint'lerini listele
     *
     * Mevcut tum webhook event turlerini listeler.
     */
    public function events()
    {
        return ApiResponse::success([
            'available_events' => [
                'order.created' => 'Yeni siparis olusturuldu',
                'order.status_changed' => 'Siparis durumu degisti',
                'order.cancelled' => 'Siparis iptal edildi',
                'product.stock_low' => 'Urun stoku azaldi',
                'product.out_of_stock' => 'Urun stoktan cikti',
                'payment.received' => 'Odeme alindi',
                'payment.failed' => 'Odeme basarisiz',
            ],
        ]);
    }

    /**
     * Yeni webhook kaydı olustur
     */
    public function store(WebhookStoreRequest $request)
    {
        $webhook = $request->user()->webhooks()->create([
            'url' => $request->url,
            'events' => $request->events,
            'secret' => Str::random(64),
            'is_active' => true,
        ]);

        return ApiResponse::success([
            'id' => $webhook->id,
            'url' => $webhook->url,
            'events' => $webhook->events,
            'secret' => $webhook->secret, // Sadece olusturulurken goster
            'created_at' => $webhook->created_at->toIso8601String(),
        ], 'Webhook basariyla olusturuldu', 201);
    }
}

Webhook payload'u da tutarli olmali:

// app/Services/WebhookDispatcher.php
public function dispatch(string $event, array $data, Webhook $webhook): void
{
    $payload = [
        'event' => $event,
        'timestamp' => now()->toIso8601String(),
        'data' => $data,
    ];

    $signature = hash_hmac('sha256', json_encode($payload), $webhook->secret);

    Http::withHeaders([
        'X-Webhook-Signature' => $signature,
        'X-Webhook-Event' => $event,
        'Content-Type' => 'application/json',
    ])->post($webhook->url, $payload);
}

8. Discovery Endpoint

AI agent'in API'nizi kesfedebilmesi icin bir "giris noktasi" sunun:

// routes/api.php
Route::get('v1', function () {
    return ApiResponse::success([
        'name' => 'ProjeMan API',
        'version' => 'v1',
        'documentation' => url('/docs/api'),
        'openapi_spec' => url('/api/v1/openapi.json'),
        'endpoints' => [
            'products' => url('/api/v1/products'),
            'categories' => url('/api/v1/categories'),
            'orders' => url('/api/v1/orders'),
            'webhooks' => url('/api/v1/webhooks'),
        ],
        'authentication' => [
            'type' => 'bearer',
            'token_endpoint' => url('/api/v1/auth/token'),
        ],
        'rate_limits' => [
            'standard' => '60 requests/minute',
            'agent' => '200 requests/minute (agent tier)',
        ],
    ]);
});

Bu endpoint, bir AI agent'in "bu API ne yapar, nasil kullanirim" sorusuna cevap verir.

9. Dogru HTTP Status Code'lari

Bu basit gorunur ama hala yanlislar yapiliyor:

200 OK          - Basarili GET, PUT, PATCH
201 Created     - Basarili POST (yeni kayit)
204 No Content  - Basarili DELETE
400 Bad Request - Genel istemci hatasi
401 Unauthorized - Kimlik dogrulama gerekli
403 Forbidden    - Yetki yok
404 Not Found    - Kaynak bulunamadi
409 Conflict     - Cakisma (ornegin unique constraint)
422 Unprocessable - Validasyon hatasi
429 Too Many     - Rate limit asildi
500 Server Error - Sunucu hatasi
503 Service Unavailable - Bakim modu

Her birini dogru kullanin. AI agent 401 gordugunde token'ini yeniler, 429'da bekler, 503'de daha sonra dener. Ama her seye 200 dondurup body'de "error": true yaparsaniz agent karisir.

10. Semantic HTML (Frontend icin)

API disinda, eger web scraping yapan AI agent'lar dusunuyorsaniz, Blade template'lerinizde semantic HTML kullanin:

<!-- DOGRU -->
<article class="product-card" itemscope itemtype="https://schema.org/Product">
    <h2 itemprop="name">{{ $product->name }}</h2>
    <span itemprop="price" content="{{ $product->price }}">
        {{ Number::currency($product->price, 'TRY') }}
    </span>
    <div itemprop="description">{{ $product->description }}</div>
    <link itemprop="availability" href="https://schema.org/InStock" />
</article>

<!-- YANLIS -->
<div class="card">
    <div class="title">{{ $product->name }}</div>
    <div class="price">{{ $product->price }} TL</div>
    <div>{{ $product->description }}</div>
</div>

Schema.org markup'i hem SEO icin hem AI icin degerli.

Sonuc

API'nizi AI-friendly yapmak aslinda iyi API tasarimi yapmakla ayni sey. Tutarli response'lar, acik dokumantasyon, dogru HTTP status code'lari, tahmin edilebilir URL'ler. Bunlari zaten yapiyor olmaliydiniz, ama simdi bunlarin onemi katlandi.

Gelecekte API tuketicilerinizin cogunlugu AI agent olacak. Buna simdi hazirlanmak, yarin avantaj saglayacak. Laravel'in convention-over-configuration felsefesi bu isi kolaylastiriyor. Zaten tutarli bir yapisi var, onu bozmamak yeterli.