# Tenant Provisioning API Documentation

## Overview

The Tenant Provisioning API allows the Admin App to request tenant database creation in the Tenant App. This is a service-to-service API protected by RS256 JWT authentication.

## Base URLs

- **Local Development:** `http://localhost:81`
- **Production:** `http://taskco-nginx` (internal Docker network)

## Authentication

All endpoints require JWT authentication with RS256 signature.

### JWT Requirements

| Claim | Value | Description |
|-------|-------|-------------|
| `iss` | `saas-admin` | Issuer (Admin App) |
| `aud` | `saas-app` | Audience (Tenant App) |
| `iat` | Current timestamp | Issued at |
| `exp` | Future timestamp | Expiration (recommend 1 hour) |
| `scope` | See below | Required scope for endpoint |

### Scopes

- `tenant:provision` - Required for provisioning endpoint
- `tenant:status` - Required for status checking endpoint

---

## Quick Start Guide

### Step 1: Generate JWT Token

**Option A: Using PHP Script (Recommended)**

```bash
cd /home/shakib/Dev/projects/saas-docker
php generate-token.php tenant:provision
```

**Option B: Using Docker Exec**

```bash
docker exec taskco-app php -r '
require_once "/var/www/html/vendor/autoload.php";
use Firebase\JWT\JWT;

$privateKey = file_get_contents("/var/www/html/storage/keys/saas_admin_jwt_private.pem");
$now = time();
$payload = [
    "iss" => "saas-admin",
    "aud" => "saas-app",
    "iat" => $now,
    "exp" => $now + 3600,
    "scope" => "tenant:provision"
];

echo JWT::encode($payload, $privateKey, "RS256");
'
```

Save the output token for use in API calls.

---

### Step 2: Call Tenant Provisioning API

**Using curl:**

```bash
TOKEN="<your-token-here>"

curl -X POST http://localhost:81/api/internal/tenants/provision \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "tenant_uid": "tnt-demo-001",
    "encrypted_credentials": "eyJkYl9ob3N0IjoibG9jYWxob3N0IiwiZGJfcG9ydCI6MzMwNiwiZGJfdXNlciI6InRlbmFudF91c2VyIiwiZGJfcGFzcyI6InNlY3VyZV9wYXNzd29yZCJ9",
    "tenant_data": {
      "company_name": "Demo Company",
      "slug": "demo-company",
      "email": "admin@demo.com",
      "admin_password": "SecureP@ss123!",
      "admin_name": "Demo Admin"
    }
  }'
```

---

## API Endpoints

### 1. Provision Tenant Database

Create a new tenant database and initialize it with default data.

**Endpoint:** `POST /api/internal/tenants/provision`

**Authentication:** Required (scope: `tenant:provision`)

**Request Headers:**
```
Content-Type: application/json
Authorization: Bearer <jwt-token>
```

**Request Body:**

```json
{
  "tenant_uid": "string (required, max:255, lowercase alphanumeric with -._)",
  "encrypted_credentials": "string (required, min:100, base64-encoded)",
  "tenant_data": {
    "company_name": "string (required, max:255)",
    "slug": "string (required, max:63, RFC 1123 subdomain format)",
    "admin_password": "string (required, min:8, max:255)",
    "email": "string (optional, valid email)",
    "phone": "string (optional, max:20)",
    "address": "string (optional, max:255)",
    "city": "string (optional, max:100)",
    "state": "string (optional, max:100)",
    "country": "string (optional, max:100)",
    "zip_code": "string (optional, max:20)",
    "domain": "string (optional, max:253, valid domain)",
    "package_id": "integer (optional, min:1)",
    "admin_name": "string (optional, max:255)",
    "admin_email": "string (optional, valid email)"
  }
}
```

**Field Details:**

| Field | Required | Type | Constraints | Description |
|-------|----------|------|-------------|-------------|
| `tenant_uid` | ✅ | string | Lowercase, alphanumeric with `-._` | Unique identifier for tenant |
| `encrypted_credentials` | ✅ | string | Min 100 chars, base64 | Encrypted database credentials |
| `tenant_data.company_name` | ✅ | string | Max 255 | Company/organization name |
| `tenant_data.slug` | ✅ | string | Max 63, RFC 1123 | Subdomain identifier (no underscores) |
| `tenant_data.admin_password` | ✅ | string | Min 8, max 255 | Admin user password |
| `tenant_data.email` | ❌ | string | Valid email | Company email address |
| `tenant_data.admin_name` | ❌ | string | Max 255 | Admin user full name |
| `tenant_data.admin_email` | ❌ | string | Valid email | Admin user email address |

**Success Response (HTTP 202 Accepted):**

```json
{
  "status": "SUCCESS",
  "code": 20200,
  "message": "Tenant database provisioning initiated",
  "details": "",
  "locale": "us",
  "data": {
    "job_id": "job_01KDVZCYZJMWZY2F0CKC5DD7PS",
    "tenant_uid": "tnt-demo-001",
    "status": "processing"
  }
}
```

**Error Responses:**

| Status | Code | Description |
|--------|------|-------------|
| 401 | 40100 | Missing or invalid JWT token |
| 403 | 40300 | Insufficient scope (wrong scope in token) |
| 422 | 42200 | Validation error (invalid request data) |

**Example Error (401 Unauthorized):**
```json
{
  "status": "ERROR",
  "code": 40100,
  "message": "Missing authorization token",
  "details": "",
  "locale": "us",
  "data": {
    "errors": null
  }
}
```

**Example Error (422 Validation Failed):**
```json
{
  "status": "ERROR",
  "code": 42200,
  "message": "Validation failed",
  "details": "",
  "locale": "us",
  "data": {
    "errors": {
      "tenant_uid": ["Tenant UID is required"],
      "tenant_data.slug": ["Tenant slug must be a valid subdomain (RFC 1123)"]
    }
  }
}
```

---

### 2. Check Provisioning Status

Check the status of a tenant provisioning job.

**Endpoint:** `GET /api/internal/tenants/provision/status/{jobId}`

**Authentication:** Required (scope: `tenant:status`)

**Path Parameters:**
- `jobId` (string, required): Job ID returned from provision endpoint

**Request Headers:**
```
Authorization: Bearer <jwt-token-with-status-scope>
```

**Success Response (HTTP 200 OK):**

```json
{
  "status": "SUCCESS",
  "code": 20000,
  "message": "OK",
  "details": "",
  "locale": "us",
  "data": {
    "job_id": "job_01KDVZCYZJMWZY2F0CKC5DD7PS",
    "status": "completed",
    "tenant_uid": "tnt-demo-001",
    "current_step": "Provisioning completed",
    "error": null,
    "completed_at": "2026-01-01T05:30:00Z"
  }
}
```

**Status Values:**
- `processing` - Job is still running
- `completed` - Provisioning succeeded
- `failed` - Provisioning failed (check `error` field)

**Error Response (404 Not Found):**
```json
{
  "status": "ERROR",
  "code": 40400,
  "message": "Job not found or expired",
  "details": "",
  "locale": "us",
  "data": {
    "errors": null
  }
}
```

---

## Complete Example Workflow

### 1. Generate Token for Provisioning

```bash
# Generate token with tenant:provision scope
TOKEN=$(php generate-token.php tenant:provision)
echo "Token: $TOKEN"
```

### 2. Provision a New Tenant

```bash
curl -X POST http://localhost:81/api/internal/tenants/provision \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "tenant_uid": "tnt-acme-corp",
    "encrypted_credentials": "eyJkYl9ob3N0IjoibG9jYWxob3N0IiwiZGJfcG9ydCI6MzMwNiwiZGJfdXNlciI6InRlbmFudF91c2VyIiwiZGJfcGFzcyI6InNlY3VyZV9wYXNzd29yZCJ9",
    "tenant_data": {
      "company_name": "ACME Corporation",
      "slug": "acme-corp",
      "email": "contact@acme.com",
      "phone": "+1-555-0123",
      "admin_name": "John Doe",
      "admin_email": "john@acme.com",
      "admin_password": "SecureP@ss123!",
      "domain": "acme-corp.taskco.tech"
    }
  }'
```

**Expected Response:**
```json
{
  "status": "SUCCESS",
  "message": "Tenant database provisioning initiated",
  "data": {
    "job_id": "job_01KDVZCYZJMWZY2F0CKC5DD7PS",
    "tenant_uid": "tnt-acme-corp",
    "status": "processing"
  }
}
```

### 3. Generate Token for Status Check

```bash
# Generate token with tenant:status scope
STATUS_TOKEN=$(php generate-token.php tenant:status)
```

### 4. Check Provisioning Status

```bash
JOB_ID="job_01KDVZCYZJMWZY2F0CKC5DD7PS"

curl -X GET "http://localhost:81/api/internal/tenants/provision/status/$JOB_ID" \
  -H "Authorization: Bearer $STATUS_TOKEN"
```

---

## Testing with Postman

### Import Collection

1. Open Postman
2. Click **Import**
3. Select file: `/home/shakib/Dev/projects/saas-docker/postman/tenant-provisioning-api.json`
4. The collection includes:
   - Token generation examples
   - Pre-configured requests
   - Example responses

### Configure Environment Variables

Create a new environment in Postman:

| Variable | Value |
|----------|-------|
| `baseUrl` | `http://localhost:81` |
| `token` | (Leave empty - will be set by token generation request) |
| `jobId` | (Leave empty - will be set by provision request) |

### Workflow in Postman

1. **Generate Token**
   - Run: `POST {{baseUrl}}/api/internal/dev/generate-token`
   - Body: `{"scope": "tenant:provision", "expires_in": 3600}`
   - Copy the token from response

2. **Provision Tenant**
   - Update the `Authorization` header with your token
   - Run: `POST {{baseUrl}}/api/internal/tenants/provision`
   - Copy the `job_id` from response

3. **Check Status** (Optional)
   - Generate a new token with scope `tenant:status`
   - Run: `GET {{baseUrl}}/api/internal/tenants/provision/status/{{jobId}}`

---

## Troubleshooting

### Error: "Missing authorization token"

**Cause:** Missing or malformed `Authorization` header

**Solution:**
```bash
# Ensure header is formatted correctly
-H "Authorization: Bearer YOUR_TOKEN_HERE"
```

---

### Error: "Token verification failed"

**Causes:**
1. Token expired
2. Wrong signature (keys don't match)
3. Invalid token format

**Solutions:**
1. Generate a new token
2. Verify keys match:
```bash
docker exec taskco-app ls -la /var/www/html/storage/keys/
```
3. Check token claims

---

### Error: "Insufficient scope"

**Cause:** Token scope doesn't match endpoint requirement

**Solution:**
- Provisioning endpoint requires: `tenant:provision`
- Status endpoint requires: `tenant:status`

Generate token with correct scope:
```bash
php generate-token.php tenant:provision  # For provisioning
php generate-token.php tenant:status     # For status check
```

---

### Error: "Validation failed"

**Cause:** Invalid request data

**Common Issues:**
1. `tenant_uid` contains uppercase letters (must be lowercase)
2. `slug` contains underscores (not allowed in subdomains)
3. `admin_password` less than 8 characters
4. `encrypted_credentials` less than 100 characters

**Solution:** Check the validation errors in response:
```json
{
  "data": {
    "errors": {
      "tenant_data.slug": ["Tenant slug must be a valid subdomain (RFC 1123)"]
    }
  }
}
```

---

## Security Notes

### Production Deployment

1. **Never expose internal API ports publicly**
   ```yaml
   # Production: Remove this from docker-compose.yml
   nginx:
     ports:
       - "81:80"  # Remove this line
   ```

2. **Use proper key management**
   - Store private keys securely (AWS Secrets Manager, HashiCorp Vault)
   - Rotate keys regularly
   - Use different keys for dev/staging/production

3. **Network isolation**
   - Keep internal APIs on private Docker network
   - Only Admin App should have access to Tenant App internal endpoints

### JWT Best Practices

1. **Short expiration times**
   - Recommended: 1 hour (`exp: now + 3600`)
   - For sensitive operations: 15 minutes

2. **Scope-based access**
   - Always use the minimum required scope
   - Don't use wildcard scopes

3. **Token storage**
   - Never log tokens
   - Don't store tokens in browser localStorage (for web apps)
   - Transmit only over HTTPS in production

---

## Support

### Check Logs

**App Logs:**
```bash
docker logs taskco-app --tail 50
```

**Laravel Logs:**
```bash
docker exec taskco-app tail -50 /var/www/html/storage/logs/laravel.log
```

**Worker Logs:**
```bash
docker logs taskco-worker --tail 50
```

### Health Check

```bash
curl http://localhost:81/health
```

Expected response: `{"status":"healthy"}`

---

## API Reference Summary

| Endpoint | Method | Scope | Description |
|----------|--------|-------|-------------|
| `/api/internal/tenants/provision` | POST | `tenant:provision` | Provision new tenant database |
| `/api/internal/tenants/provision/status/{jobId}` | GET | `tenant:status` | Check provisioning status |

**Base URL (Local):** `http://localhost:81`

**Authentication:** RS256 JWT in `Authorization: Bearer <token>` header

**Response Format:** JSON with standardized structure
```json
{
  "status": "SUCCESS|ERROR",
  "code": 20200,
  "message": "...",
  "data": { ... }
}
```
