Priority Build Implementation Plan¶
Created: February 4, 2026
Based on: docs/audits/PRICING_CLAIMS_INFRASTRUCTURE_AUDIT_2026-02-04.md
Status: Pre-GTM Critical Items
Overview¶
This plan covers three critical areas that must be completed before GTM launch:
| Area | Current Score | Target | Effort Estimate |
|---|---|---|---|
| API Controllers | 20% | 100% | 3-4 days |
| Security Critical Fixes | 33% | 90%+ | 2-3 days |
| Rate Limit Tier Differentiation | 0% | 100% | 1 day |
Total Estimated Effort: 6-8 days (can be parallelized)
1. API Controllers Implementation¶
Current State¶
- Routes defined in
routes/api_v1.php(lines 284-318) - Controllers referenced but DO NOT EXIST
- Customers hitting these endpoints get 404 errors
Missing Controllers¶
| Controller | Methods Needed | Priority |
|---|---|---|
PartnerController |
profile() |
HIGH |
PartnerListingController |
index(), show(), update(), seoScore(), recommendations() |
HIGH |
CoSellController |
index(), store(), show(), update(), timeline() |
HIGH |
PartnerAnalyticsController |
summary(), reports(), showReport() |
MEDIUM |
WebhookController |
index(), store(), destroy(), events(), test() |
HIGH |
Implementation Steps¶
Step 1.1: Create Base Partner Controller¶
File: app/Http/Controllers/Api/V1/PartnerController.php
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* @OA\Tag(name="Partner", description="Partner profile and settings")
*/
class PartnerController extends Controller
{
/**
* @OA\Get(
* path="/api/v1/partners/profile",
* summary="Get partner profile",
* tags={"Partner"},
* security={{"bearerAuth":{}}},
* @OA\Response(response=200, description="Partner profile data")
* )
*/
public function profile(Request $request): JsonResponse
{
$user = $request->user();
$partner = $user->partner;
if (!$partner) {
return response()->json([
'error' => 'No partner profile found',
'code' => 'PARTNER_NOT_FOUND'
], 404);
}
return response()->json([
'data' => [
'id' => $partner->id,
'name' => $partner->name,
'company' => $partner->company_name,
'tier' => $user->activePlan()?->getTier()?->value,
'created_at' => $partner->created_at,
],
'meta' => [
'api_version' => 'v1',
]
]);
}
}
Step 1.2: Create Partner Listing Controller¶
File: app/Http/Controllers/Api/V1/PartnerListingController.php
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\CustomExtensions\CloudMarketplace\System\Models\Listing;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
/**
* @OA\Tag(name="Listings", description="Partner marketplace listings")
*/
class PartnerListingController extends Controller
{
/**
* List all listings for the authenticated partner
*/
public function index(Request $request): JsonResponse
{
$user = $request->user();
$listings = Listing::where('user_id', $user->id)
->orderBy('updated_at', 'desc')
->paginate($request->get('per_page', 20));
return response()->json([
'data' => $listings->items(),
'meta' => [
'current_page' => $listings->currentPage(),
'last_page' => $listings->lastPage(),
'per_page' => $listings->perPage(),
'total' => $listings->total(),
]
]);
}
/**
* Get a specific listing
*/
public function show(Request $request, int $id): JsonResponse
{
$user = $request->user();
$listing = Listing::where('id', $id)
->where('user_id', $user->id)
->first();
if (!$listing) {
return response()->json([
'error' => 'Listing not found',
'code' => 'LISTING_NOT_FOUND'
], 404);
}
return response()->json(['data' => $listing]);
}
/**
* Update a listing
*/
public function update(Request $request, int $id): JsonResponse
{
$user = $request->user();
$listing = Listing::where('id', $id)
->where('user_id', $user->id)
->first();
if (!$listing) {
return response()->json([
'error' => 'Listing not found',
'code' => 'LISTING_NOT_FOUND'
], 404);
}
$validator = Validator::make($request->all(), [
'title' => 'sometimes|string|max:255',
'description' => 'sometimes|string',
'status' => 'sometimes|in:draft,active,archived',
]);
if ($validator->fails()) {
return response()->json([
'error' => 'Validation failed',
'details' => $validator->errors()
], 422);
}
$listing->update($validator->validated());
return response()->json(['data' => $listing->fresh()]);
}
/**
* Get SEO score for a listing
*/
public function seoScore(Request $request, int $id): JsonResponse
{
$user = $request->user();
$listing = Listing::where('id', $id)
->where('user_id', $user->id)
->first();
if (!$listing) {
return response()->json([
'error' => 'Listing not found',
'code' => 'LISTING_NOT_FOUND'
], 404);
}
// TODO: Integrate with MarketplaceListingSEOCapability
return response()->json([
'data' => [
'listing_id' => $id,
'overall_score' => $listing->seo_score ?? 0,
'factors' => [
'title_optimization' => $listing->seo_title_score ?? 0,
'description_quality' => $listing->seo_description_score ?? 0,
'keyword_density' => $listing->seo_keyword_score ?? 0,
],
'last_analyzed_at' => $listing->seo_analyzed_at,
]
]);
}
/**
* Get SEO recommendations for a listing
*/
public function recommendations(Request $request, int $id): JsonResponse
{
$user = $request->user();
$listing = Listing::where('id', $id)
->where('user_id', $user->id)
->first();
if (!$listing) {
return response()->json([
'error' => 'Listing not found',
'code' => 'LISTING_NOT_FOUND'
], 404);
}
// TODO: Integrate with MarketplaceListingSEOCapability for AI recommendations
return response()->json([
'data' => [
'listing_id' => $id,
'recommendations' => $listing->seo_recommendations ?? [],
]
]);
}
}
Step 1.3: Create CoSell Controller¶
File: app/Http/Controllers/Api/V1/CoSellController.php
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\CustomExtensions\CloudMarketplace\System\Models\CoSellOpportunity;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
/**
* @OA\Tag(name="CoSell", description="Co-sell opportunity management")
*/
class CoSellController extends Controller
{
public function index(Request $request): JsonResponse
{
$user = $request->user();
$opportunities = CoSellOpportunity::where('user_id', $user->id)
->orderBy('updated_at', 'desc')
->paginate($request->get('per_page', 20));
return response()->json([
'data' => $opportunities->items(),
'meta' => [
'current_page' => $opportunities->currentPage(),
'last_page' => $opportunities->lastPage(),
'per_page' => $opportunities->perPage(),
'total' => $opportunities->total(),
]
]);
}
public function store(Request $request): JsonResponse
{
$validator = Validator::make($request->all(), [
'partner_id' => 'required|exists:partners,id',
'opportunity_name' => 'required|string|max:255',
'customer_name' => 'required|string|max:255',
'estimated_value' => 'required|numeric|min:0',
'stage' => 'required|in:prospect,qualified,proposal,negotiation,closed_won,closed_lost',
'expected_close_date' => 'required|date|after:today',
]);
if ($validator->fails()) {
return response()->json([
'error' => 'Validation failed',
'details' => $validator->errors()
], 422);
}
$opportunity = CoSellOpportunity::create([
'user_id' => $request->user()->id,
...$validator->validated()
]);
return response()->json(['data' => $opportunity], 201);
}
public function show(Request $request, int $id): JsonResponse
{
$user = $request->user();
$opportunity = CoSellOpportunity::where('id', $id)
->where('user_id', $user->id)
->first();
if (!$opportunity) {
return response()->json([
'error' => 'Opportunity not found',
'code' => 'OPPORTUNITY_NOT_FOUND'
], 404);
}
return response()->json(['data' => $opportunity]);
}
public function update(Request $request, int $id): JsonResponse
{
$user = $request->user();
$opportunity = CoSellOpportunity::where('id', $id)
->where('user_id', $user->id)
->first();
if (!$opportunity) {
return response()->json([
'error' => 'Opportunity not found',
'code' => 'OPPORTUNITY_NOT_FOUND'
], 404);
}
$validator = Validator::make($request->all(), [
'opportunity_name' => 'sometimes|string|max:255',
'customer_name' => 'sometimes|string|max:255',
'estimated_value' => 'sometimes|numeric|min:0',
'stage' => 'sometimes|in:prospect,qualified,proposal,negotiation,closed_won,closed_lost',
'expected_close_date' => 'sometimes|date',
]);
if ($validator->fails()) {
return response()->json([
'error' => 'Validation failed',
'details' => $validator->errors()
], 422);
}
$opportunity->update($validator->validated());
return response()->json(['data' => $opportunity->fresh()]);
}
public function timeline(Request $request, int $id): JsonResponse
{
$user = $request->user();
$opportunity = CoSellOpportunity::where('id', $id)
->where('user_id', $user->id)
->with('activities')
->first();
if (!$opportunity) {
return response()->json([
'error' => 'Opportunity not found',
'code' => 'OPPORTUNITY_NOT_FOUND'
], 404);
}
return response()->json([
'data' => [
'opportunity_id' => $id,
'timeline' => $opportunity->activities ?? [],
]
]);
}
}
Step 1.4: Create Partner Analytics Controller¶
File: app/Http/Controllers/Api/V1/PartnerAnalyticsController.php
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* @OA\Tag(name="Analytics", description="Partner analytics and reporting")
*/
class PartnerAnalyticsController extends Controller
{
public function summary(Request $request): JsonResponse
{
$user = $request->user();
$period = $request->get('period', '30d');
// TODO: Integrate with actual analytics service
return response()->json([
'data' => [
'period' => $period,
'listings' => [
'total' => 0,
'active' => 0,
'views' => 0,
],
'cosell' => [
'opportunities' => 0,
'pipeline_value' => 0,
'closed_won' => 0,
],
'content' => [
'generated' => 0,
'credits_used' => 0,
],
]
]);
}
public function reports(Request $request): JsonResponse
{
$user = $request->user();
// TODO: Integrate with reporting system
return response()->json([
'data' => [],
'meta' => [
'available_reports' => [
'listing_performance',
'cosell_pipeline',
'content_usage',
'attribution',
]
]
]);
}
public function showReport(Request $request, string $reportType): JsonResponse
{
$user = $request->user();
$validReports = ['listing_performance', 'cosell_pipeline', 'content_usage', 'attribution'];
if (!in_array($reportType, $validReports)) {
return response()->json([
'error' => 'Invalid report type',
'code' => 'INVALID_REPORT_TYPE',
'valid_types' => $validReports
], 400);
}
// TODO: Generate actual report data
return response()->json([
'data' => [
'report_type' => $reportType,
'generated_at' => now()->toISOString(),
'data' => [],
]
]);
}
}
Step 1.5: Create Webhook Controller¶
File: app/Http/Controllers/Api/V1/WebhookController.php
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Models\Webhook;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
/**
* @OA\Tag(name="Webhooks", description="Webhook management")
*/
class WebhookController extends Controller
{
public function index(Request $request): JsonResponse
{
$user = $request->user();
$webhooks = Webhook::where('user_id', $user->id)
->orderBy('created_at', 'desc')
->paginate($request->get('per_page', 20));
return response()->json([
'data' => $webhooks->items(),
'meta' => [
'current_page' => $webhooks->currentPage(),
'last_page' => $webhooks->lastPage(),
'per_page' => $webhooks->perPage(),
'total' => $webhooks->total(),
]
]);
}
public function store(Request $request): JsonResponse
{
$validator = Validator::make($request->all(), [
'url' => 'required|url|max:2048',
'events' => 'required|array|min:1',
'events.*' => 'string|in:listing.created,listing.updated,cosell.created,cosell.updated,content.generated',
'secret' => 'sometimes|string|min:32',
'active' => 'sometimes|boolean',
]);
if ($validator->fails()) {
return response()->json([
'error' => 'Validation failed',
'details' => $validator->errors()
], 422);
}
$data = $validator->validated();
$data['user_id'] = $request->user()->id;
$data['secret'] = $data['secret'] ?? Str::random(64);
$data['active'] = $data['active'] ?? true;
$webhook = Webhook::create($data);
return response()->json([
'data' => $webhook,
'meta' => [
'secret_shown_once' => $data['secret'],
]
], 201);
}
public function destroy(Request $request, int $id): JsonResponse
{
$user = $request->user();
$webhook = Webhook::where('id', $id)
->where('user_id', $user->id)
->first();
if (!$webhook) {
return response()->json([
'error' => 'Webhook not found',
'code' => 'WEBHOOK_NOT_FOUND'
], 404);
}
$webhook->delete();
return response()->json(null, 204);
}
public function events(Request $request): JsonResponse
{
return response()->json([
'data' => [
'events' => [
[
'name' => 'listing.created',
'description' => 'Triggered when a new listing is created',
],
[
'name' => 'listing.updated',
'description' => 'Triggered when a listing is updated',
],
[
'name' => 'cosell.created',
'description' => 'Triggered when a new co-sell opportunity is created',
],
[
'name' => 'cosell.updated',
'description' => 'Triggered when a co-sell opportunity is updated',
],
[
'name' => 'content.generated',
'description' => 'Triggered when AI content is generated',
],
]
]
]);
}
public function test(Request $request, int $id): JsonResponse
{
$user = $request->user();
$webhook = Webhook::where('id', $id)
->where('user_id', $user->id)
->first();
if (!$webhook) {
return response()->json([
'error' => 'Webhook not found',
'code' => 'WEBHOOK_NOT_FOUND'
], 404);
}
// TODO: Dispatch test webhook job
// TestWebhookJob::dispatch($webhook);
return response()->json([
'data' => [
'message' => 'Test webhook dispatched',
'webhook_id' => $id,
]
]);
}
}
Step 1.6: Add Authentication Middleware to Routes¶
File: routes/api_v1.php - Update partner routes (around line 282)
// Current (missing auth):
Route::prefix('partners')->middleware(['throttle:partner'])->group(function () {
// Updated (with auth):
Route::prefix('partners')->middleware(['auth:api', 'throttle:partner'])->group(function () {
2. Security Critical Fixes¶
Issue Tracker¶
| # | Issue | Severity | File | Status |
|---|---|---|---|---|
| 1 | CORS wildcards | CRITICAL | config/cors.php |
FIXED |
| 2 | Broad CSRF exemptions | CRITICAL | app/Http/Middleware/VerifyCsrfToken.php |
TODO |
| 3 | Session SameSite=none | CRITICAL | config/session.php |
TODO |
| 4 | Missing authorization in DocumentsApiController | CRITICAL | app/Http/Controllers/Api/DocumentsApiController.php |
TODO |
| 5 | Weak OTP (4 digits) | CRITICAL | app/Http/Controllers/Auth/AuthenticatedSessionController.php |
TODO |
| 6 | XSS in Blade templates | CRITICAL | Multiple views | TODO |
| 7 | Mass assignment vulnerabilities | HIGH | 25+ models | TODO |
| 8 | Session encryption disabled | HIGH | config/session.php |
TODO |
Fix 2.1: Remove Broad CSRF Exemption¶
File: app/Http/Middleware/VerifyCsrfToken.php
Current (lines 18-31):
protected $except = [
'pdf/getContent',
'stripe/*',
'webhooks/*',
'dashboard/*', // <-- REMOVE THIS LINE
'dashboard/user/payment/iyzico/*',
'chatbot/*',
'generator/webhook/fal-ai',
'dashboard/admin/config/more',
'translations/lang/update-all',
'social-media/*',
'chatbot/instagram/*',
];
Fixed:
protected $except = [
'pdf/getContent',
'stripe/*',
'webhooks/*',
// Specific dashboard endpoints that legitimately need CSRF exemption
'dashboard/user/payment/iyzico/*',
'chatbot/*',
'generator/webhook/fal-ai',
'dashboard/admin/config/more',
'translations/lang/update-all',
'social-media/*',
'chatbot/instagram/*',
];
Fix 2.2: Update Session Configuration¶
File: config/session.php
Line 199 - Change SameSite:
Line 49 - Enable session encryption:
Fix 2.3: Add Authorization to DocumentsApiController¶
File: app/Http/Controllers/Api/DocumentsApiController.php
Method: getDoc() (lines 300-315)
public function getDoc(Request $request)
{
if ($request->id == null) {
return response()->json(['error' => __('ID is required')], 412);
}
$document = UserOpenai::select('user_openai.*', 'openai.title as ai_title', 'openai.image as ai_image', 'openai.color as ai_color')
->join('openai', 'openai.id', '=', 'user_openai.openai_id')
->where('user_openai.id', $request->id)
->where('user_openai.user_id', auth()->id()) // ADD THIS LINE
->first();
if ($document == null) {
return response()->json(['error' => __('Document not found')], 404);
}
return response()->json($document, 200);
}
Method: saveDoc() (lines 381-407)
Method: deleteDoc() (lines 454-472)
Fix 2.4: Strengthen OTP Implementation¶
File: app/Http/Controllers/Auth/AuthenticatedSessionController.php (line 84)
Current:
Fixed:
Also add rate limiting for OTP verification route in routes/web.php or routes/api.php:
Route::post('/verify-otp', [AuthenticatedSessionController::class, 'verifyOtp'])
->middleware('throttle:5,15'); // 5 attempts per 15 minutes
Fix 2.5: Sanitize XSS in Blade Templates¶
Option A: Use HTMLPurifier (Recommended)
Install: composer require ezyang/htmlpurifier
Create helper in app/Helpers/helpers.php:
function clean_html(string $html): string
{
$config = HTMLPurifier_Config::createDefault();
$config->set('HTML.Allowed', 'p,br,strong,em,ul,ol,li,a[href],h1,h2,h3,h4,h5,h6,blockquote,pre,code');
$purifier = new HTMLPurifier($config);
return $purifier->purify($html);
}
Option B: Use Laravel's built-in e() for simple cases
Files to update:
- resources/views/social-media-front/page/index.blade.php (line 26)
- resources/views/default/page/index.blade.php (line 41)
- resources/views/social-media-front/blog/post.blade.php (line 19)
- resources/views/default/blog/post.blade.php (line 19)
Change:
To:
Fix 2.6: Update Mass Assignment Protection¶
For each model with protected $guarded = [], convert to explicit $fillable:
Example for app/Models/UserSupport.php:
// Current:
protected $guarded = [];
// Fixed:
protected $fillable = [
'user_id',
'subject',
'message',
'status',
'priority',
'category',
];
Priority models to fix:
1. UserSupport.php
2. UserOpenai.php
3. Activity.php
4. OpenAIGenerator.php
5. Setting.php
3. Rate Limit Tier Differentiation¶
Current State¶
- All users share same rate limits (60 req/min for API, 100 req/min for partner API)
- No tier-based differentiation
Target State (Per Pricing Claims)¶
| Tier | Rate Limit | API Keys |
|---|---|---|
| Starter | 60 req/min | 1 |
| Accelerate | 100 req/min | 1 |
| Command | 1000 req/min | Unlimited |
Implementation¶
File: app/Providers/RouteServiceProvider.php
Replace lines 41-58 with:
use App\Enums\Plan\PlanTier;
protected function configureRateLimiting(): void
{
// Default API rate limit (unauthenticated or free users)
RateLimiter::for('api', static function (Request $request) {
$user = $request->user();
if (!$user) {
return Limit::perMinute(30)->by($request->ip());
}
$tier = $user->activePlan()?->getTier();
return match ($tier) {
PlanTier::STARTER,
PlanTier::MARKETPLACE_STARTER => Limit::perMinute(60)->by($user->id),
PlanTier::ACCELERATE,
PlanTier::MARKETPLACE_ACCELERATE => Limit::perMinute(100)->by($user->id),
PlanTier::COMMAND_PLUS,
PlanTier::MARKETPLACE_COMMAND_PLUS => Limit::perMinute(1000)->by($user->id),
PlanTier::ENTERPRISE => Limit::perMinute(5000)->by($user->id),
default => Limit::perMinute(60)->by($user->id),
};
});
// Partner API rate limit (tier-based)
RateLimiter::for('partner', static function (Request $request) {
$user = $request->user();
$partnerId = $user?->partner_id ?? $request->header('X-Partner-Id');
if (!$user) {
return Limit::perMinute(30)->by($request->ip());
}
$tier = $user->activePlan()?->getTier();
return match ($tier) {
PlanTier::STARTER,
PlanTier::MARKETPLACE_STARTER => [
Limit::perMinute(60)->by($partnerId ?: $user->id),
Limit::perHour(1000)->by($partnerId ?: $user->id),
],
PlanTier::ACCELERATE,
PlanTier::MARKETPLACE_ACCELERATE => [
Limit::perMinute(100)->by($partnerId ?: $user->id),
Limit::perHour(2000)->by($partnerId ?: $user->id),
],
PlanTier::COMMAND_PLUS,
PlanTier::MARKETPLACE_COMMAND_PLUS => [
Limit::perMinute(1000)->by($partnerId ?: $user->id),
Limit::perHour(20000)->by($partnerId ?: $user->id),
],
PlanTier::ENTERPRISE => [
Limit::perMinute(5000)->by($partnerId ?: $user->id),
Limit::perHour(100000)->by($partnerId ?: $user->id),
],
default => [
Limit::perMinute(60)->by($partnerId ?: $user->id),
Limit::perHour(1000)->by($partnerId ?: $user->id),
],
};
});
}
Add Rate Limit Headers Middleware (Optional but Recommended)¶
File: app/Http/Middleware/AddRateLimitHeaders.php
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class AddRateLimitHeaders
{
public function handle(Request $request, Closure $next)
{
$response = $next($request);
$user = $request->user();
$tier = $user?->activePlan()?->getTier()?->value ?? 'free';
$response->headers->set('X-RateLimit-Tier', $tier);
return $response;
}
}
Register in app/Http/Kernel.php under $middlewareGroups['api'].
4. Implementation Checklist¶
Week 1: API Controllers (3-4 days)¶
- Create
app/Http/Controllers/Api/V1/directory structure - Implement
PartnerController.php - Implement
PartnerListingController.php - Implement
CoSellController.php - Implement
PartnerAnalyticsController.php - Implement
WebhookController.php - Create
Webhookmodel and migration if not exists - Add
auth:apimiddleware to partner routes - Write API tests for each controller
- Update OpenAPI documentation
Week 1: Security Fixes (2-3 days, can parallelize)¶
- Remove
dashboard/*from CSRF exemptions - Update session
same_sitetolax - Enable session encryption
- Add user ownership checks to
DocumentsApiController - Upgrade OTP from 4 to 6 digits
- Add rate limiting to OTP verification
- Install HTMLPurifier and create
clean_html()helper - Update Blade templates to use sanitized output
- Convert top 5 models from
$guarded = []to$fillable - Run security regression tests
Week 1: Rate Limiting (1 day)¶
- Update
RouteServiceProvider.phpwith tier-based logic - Add
AddRateLimitHeadersmiddleware - Test rate limits for each tier
- Document rate limits in API docs
5. Testing Strategy¶
API Controller Tests¶
// tests/Feature/Api/V1/PartnerListingControllerTest.php
public function test_user_can_only_access_own_listings()
{
$user1 = User::factory()->create();
$user2 = User::factory()->create();
$listing = Listing::factory()->create(['user_id' => $user1->id]);
$this->actingAs($user2, 'api')
->getJson("/api/v1/partners/listings/{$listing->id}")
->assertStatus(404);
}
Security Tests¶
// tests/Feature/Security/DocumentAuthorizationTest.php
public function test_user_cannot_access_other_users_documents()
{
$user1 = User::factory()->create();
$user2 = User::factory()->create();
$doc = UserOpenai::factory()->create(['user_id' => $user1->id]);
$this->actingAs($user2, 'api')
->postJson('/api/documents/get', ['id' => $doc->id])
->assertStatus(404);
}
Rate Limit Tests¶
// tests/Feature/RateLimitTest.php
public function test_starter_tier_has_60_requests_per_minute()
{
$user = User::factory()->withPlan('starter')->create();
for ($i = 0; $i < 60; $i++) {
$this->actingAs($user, 'api')
->getJson('/api/v1/partners/profile')
->assertStatus(200);
}
$this->actingAs($user, 'api')
->getJson('/api/v1/partners/profile')
->assertStatus(429);
}
6. Rollback Plan¶
If issues arise after deployment:
- API Controllers: Can be reverted by removing controllers and routes will 404 (same as current state)
- Security Fixes:
- CSRF: Re-add
dashboard/*exemption if legitimate flows break - Session: Revert
same_sitetononeif third-party integrations break - Rate Limits: Revert to flat limits in
RouteServiceProvider.php
Plan created by Claude Code on February 4, 2026