# [Usage](../../../usage.md) > API

The K-Framework integrates a mini-suite for easy API handling. In the k-framework config file `api.php`, you will find:

```php
<?php

return [
    'enabled' => false,
    'endpoint' => '/kfw_api/v1/request',
];
```

where you can enable/disable the API and configure its endpoint. The method for the endpoint is `POST`.

You can also enable a module in `modules.php`:
```php
'api_credentials_resource' => true,
```
that will enable the Resource for handling graphically the API credentials. You will be able to:
- Create/edit/delete credentials
- Enable/disable credentials without deleting them
- Set the maximum number of daily queries (set `-1` for unlimited)
- Handling permissions for each single table of the schema:
  - `SELECT`
  - `INSERT`
  - `UPDATE`
  - `DELETE`
- A configurable enable/disable list of allowed IP.

Below are some examples.

## Laravel HTTP client with Basic Auth

### Select all users

```php
$response = Http::withBasicAuth($this->user, $this->pass)
    ->acceptJson()
    ->asJson()
    ->post($this->url, [
        'operation' => 'select',
        'table_name' => 'users',
    ]);

$data = $response->json();
```

### Select with filters

```php
$response = Http::withBasicAuth($this->user, $this->pass)
    ->acceptJson()
    ->asJson()
    ->post($this->url, [
        'operation' => 'select',
        'table_name' => 'users',
        'select' => ['id', 'name', 'email'],
        'filters' => [
            [
                'column' => 'status',
                'operator' => '=',
                'value' => 'active',
            ],
        ],
        'order_by' => [
            [
                'column' => 'id',
                'direction' => 'desc',
            ],
        ],
        'limit' => 10,
    ]);

$data = $response->json();
```

### Select with raw query

```php
$response = Http::withBasicAuth($this->user, $this->pass)
    ->acceptJson()
    ->asJson()
    ->post($this->url, [
        'query' => 'select id, name, email from users where status = ? order by id desc limit 10',
        'bindings' => ['active'],
    ]);

$data = $response->json();
```

### Insert one user

```php
$response = Http::withBasicAuth($this->user, $this->pass)
    ->acceptJson()
    ->asJson()
    ->post($this->url, [
        'operation' => 'insert',
        'table_name' => 'users',
        'data' => [
            'name' => 'John Doe',
            'email' => 'john@example.com',
            'password' => 'secret',
        ],
    ]);

$data = $response->json();
```

### Insert multiple users

```php
$response = Http::withBasicAuth($this->user, $this->pass)
    ->acceptJson()
    ->asJson()
    ->post($this->url, [
        'operation' => 'insert',
        'table_name' => 'users',
        'data' => [
            [
                'name' => 'John Doe',
                'email' => 'john@example.com',
                'password' => 'secret',
            ],
            [
                'name' => 'Jane Doe',
                'email' => 'jane@example.com',
                'password' => 'secret',
            ],
        ],
    ]);

$data = $response->json();
```

### Update one user

```php
$response = Http::withBasicAuth($this->user, $this->pass)
    ->acceptJson()
    ->asJson()
    ->post($this->url, [
        'operation' => 'update',
        'table_name' => 'users',
        'filters' => [
            [
                'column' => 'id',
                'operator' => '=',
                'value' => 1,
            ],
        ],
        'data' => [
            'name' => 'John Updated',
            'email' => 'john.updated@example.com',
        ],
    ]);

$data = $response->json();
```

### Delete one user

```php
$response = Http::withBasicAuth($this->user, $this->pass)
    ->acceptJson()
    ->asJson()
    ->post($this->url, [
        'operation' => 'delete',
        'table_name' => 'users',
        'filters' => [
            [
                'column' => 'id',
                'operator' => '=',
                'value' => 1,
            ],
        ],
    ]);

$data = $response->json();
```

### Join example

```php
$response = Http::withBasicAuth($this->user, $this->pass)
    ->acceptJson()
    ->asJson()
    ->post($this->url, [
        'operation' => 'select',
        'table_name' => 'users',
        'select' => [
            ['column' => 'users.id', 'alias' => 'user_id'],
            ['column' => 'users.name', 'alias' => 'user_name'],
            ['column' => 'orders.total', 'alias' => 'order_total'],
        ],
        'joins' => [
            [
                'type' => 'left',
                'table_name' => 'orders',
                'first' => 'users.id',
                'operator' => '=',
                'second' => 'orders.user_id',
            ],
        ],
        'filters' => [
            [
                'column' => 'users.status',
                'operator' => '=',
                'value' => 'active',
            ],
        ],
    ]);

$data = $response->json();
```

### Aggregate example

```php
$response = Http::withBasicAuth($this->user, $this->pass)
    ->acceptJson()
    ->asJson()
    ->post($this->url, [
        'operation' => 'select',
        'table_name' => 'orders',
        'select' => ['user_id'],
        'aggregates' => [
            [
                'function' => 'COUNT',
                'column' => '*',
                'alias' => 'orders_count',
            ],
            [
                'function' => 'SUM',
                'column' => 'total',
                'alias' => 'total_amount',
            ],
        ],
        'group_by' => ['user_id'],
        'having' => [
            [
                'function' => 'COUNT',
                'column' => '*',
                'operator' => '>',
                'value' => 1,
            ],
        ],
        'order_by' => [
            [
                'column' => 'total_amount',
                'direction' => 'desc',
            ],
        ],
    ]);

$data = $response->json();
```

## Laravel HTTP client with custom headers

### Select all users

```php
$response = Http::withHeaders([
        'username' => $this->user,
        'password' => $this->pass,
    ])
    ->acceptJson()
    ->asJson()
    ->post($this->url, [
        'operation' => 'select',
        'table_name' => 'users',
    ]);

$data = $response->json();
```

### Select with raw query

```php
$response = Http::withHeaders([
        'username' => $this->user,
        'password' => $this->pass,
    ])
    ->acceptJson()
    ->asJson()
    ->post($this->url, [
        'query' => 'select id, name, email from users where status = ? order by id desc limit 10',
        'bindings' => ['active'],
    ]);

$data = $response->json();
```

### Insert one user

```php
$response = Http::withHeaders([
        'username' => $this->user,
        'password' => $this->pass,
    ])
    ->acceptJson()
    ->asJson()
    ->post($this->url, [
        'operation' => 'insert',
        'table_name' => 'users',
        'data' => [
            'name' => 'John Doe',
            'email' => 'john@example.com',
            'password' => 'secret',
        ],
    ]);

$data = $response->json();
```

### Update one user

```php
$response = Http::withHeaders([
        'username' => $this->user,
        'password' => $this->pass,
    ])
    ->acceptJson()
    ->asJson()
    ->post($this->url, [
        'operation' => 'update',
        'table_name' => 'users',
        'filters' => [
            [
                'column' => 'id',
                'operator' => '=',
                'value' => 1,
            ],
        ],
        'data' => [
            'name' => 'John Updated',
            'email' => 'john.updated@example.com',
        ],
    ]);

$data = $response->json();
```

### Delete one user

```php
$response = Http::withHeaders([
        'username' => $this->user,
        'password' => $this->pass,
    ])
    ->acceptJson()
    ->asJson()
    ->post($this->url, [
        'operation' => 'delete',
        'table_name' => 'users',
        'filters' => [
            [
                'column' => 'id',
                'operator' => '=',
                'value' => 1,
            ],
        ],
    ]);

$data = $response->json();
```

## cURL with Basic Auth

### Select all users

```bash
curl -X POST "https://your-domain.com/your-api-endpoint" \
  -u "your_username:your_password" \
  -H "Content-Type: application/json" \
  -d '{
    "operation": "select",
    "table_name": "users"
  }'
```

### Select with raw query

```bash
curl -X POST "https://your-domain.com/your-api-endpoint" \
  -u "your_username:your_password" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "select id, name, email from users where status = ? order by id desc limit 10",
    "bindings": ["active"]
  }'
```

### Insert one user

```bash
curl -X POST "https://your-domain.com/your-api-endpoint" \
  -u "your_username:your_password" \
  -H "Content-Type: application/json" \
  -d '{
    "operation": "insert",
    "table_name": "users",
    "data": {
      "name": "John Doe",
      "email": "john@example.com",
      "password": "secret"
    }
  }'
```

### Update one user

```bash
curl -X POST "https://your-domain.com/your-api-endpoint" \
  -u "your_username:your_password" \
  -H "Content-Type: application/json" \
  -d '{
    "operation": "update",
    "table_name": "users",
    "filters": [
      {
        "column": "id",
        "operator": "=",
        "value": 1
      }
    ],
    "data": {
      "name": "John Updated",
      "email": "john.updated@example.com"
    }
  }'
```

### Delete one user

```bash
curl -X POST "https://your-domain.com/your-api-endpoint" \
  -u "your_username:your_password" \
  -H "Content-Type: application/json" \
  -d '{
    "operation": "delete",
    "table_name": "users",
    "filters": [
      {
        "column": "id",
        "operator": "=",
        "value": 1
      }
    ]
  }'
```

## cURL with custom headers

### Select all users

```bash
curl -X POST "https://your-domain.com/your-api-endpoint" \
  -H "Content-Type: application/json" \
  -H "username: your_username" \
  -H "password: your_password" \
  -d '{
    "operation": "select",
    "table_name": "users"
  }'
```

### Insert one user

```bash
curl -X POST "https://your-domain.com/your-api-endpoint" \
  -H "Content-Type: application/json" \
  -H "username: your_username" \
  -H "password: your_password" \
  -d '{
    "operation": "insert",
    "table_name": "users",
    "data": {
      "name": "John Doe",
      "email": "john@example.com",
      "password": "secret"
    }
  }'
```

### Insert multiple users

```bash
curl -X POST "https://your-domain.com/your-api-endpoint" \
  -H "Content-Type: application/json" \
  -H "username: your_username" \
  -H "password: your_password" \
  -d '{
    "operation": "insert",
    "table_name": "users",
    "data": [
      {
        "name": "John Doe",
        "email": "john@example.com",
        "password": "secret"
      },
      {
        "name": "Jane Doe",
        "email": "jane@example.com",
        "password": "secret"
      }
    ]
  }'
```

### Update one user

```bash
curl -X POST "https://your-domain.com/your-api-endpoint" \
  -H "Content-Type: application/json" \
  -H "username: your_username" \
  -H "password: your_password" \
  -d '{
    "operation": "update",
    "table_name": "users",
    "filters": [
      {
        "column": "id",
        "operator": "=",
        "value": 1
      }
    ],
    "data": {
      "name": "John Updated",
      "email": "john.updated@example.com"
    }
  }'
```

### Delete one user

```bash
curl -X POST "https://your-domain.com/your-api-endpoint" \
  -H "Content-Type: application/json" \
  -H "username: your_username" \
  -H "password: your_password" \
  -d '{
    "operation": "delete",
    "table_name": "users",
    "filters": [
      {
        "column": "id",
        "operator": "=",
        "value": 1
      }
    ]
  }'
```

### Join between users and orders

```bash
curl -X POST "https://your-domain.com/your-api-endpoint" \
  -H "Content-Type: application/json" \
  -H "username: your_username" \
  -H "password: your_password" \
  -d '{
    "operation": "select",
    "table_name": "users",
    "select": [
      { "column": "users.id", "alias": "user_id" },
      { "column": "users.name", "alias": "user_name" },
      { "column": "orders.id", "alias": "order_id" },
      { "column": "orders.total", "alias": "order_total" }
    ],
    "joins": [
      {
        "type": "inner",
        "table_name": "orders",
        "first": "users.id",
        "operator": "=",
        "second": "orders.user_id"
      }
    ]
  }'
```

### Join with filters

```bash
curl -X POST "https://your-domain.com/your-api-endpoint" \
  -H "Content-Type: application/json" \
  -H "username: your_username" \
  -H "password: your_password" \
  -d '{
    "operation": "select",
    "table_name": "users",
    "select": [
      { "column": "users.id", "alias": "user_id" },
      { "column": "users.name", "alias": "user_name" },
      { "column": "orders.total", "alias": "order_total" }
    ],
    "joins": [
      {
        "type": "left",
        "table_name": "orders",
        "first": "users.id",
        "operator": "=",
        "second": "orders.user_id"
      }
    ],
    "filters": [
      {
        "column": "users.status",
        "operator": "=",
        "value": "active"
      },
      {
        "column": "orders.total",
        "operator": ">",
        "value": 100
      }
    ],
    "order_by": [
      {
        "column": "orders.total",
        "direction": "desc"
      }
    ]
  }'
```

### A complex operation

```bash
curl -X POST "https://your-domain.com/your-api-endpoint" \
  -H "Content-Type: application/json" \
  -H "username: your_username" \
  -H "password: your_password" \
  -d '{
    "operation": "select",
    "table_name": "users",
    "select": [
      { "column": "users.id", "alias": "user_id" },
      { "column": "users.name", "alias": "user_name" }
    ],
    "joins": [
      {
        "type": "left",
        "table_name": "orders",
        "first": "users.id",
        "operator": "=",
        "second": "orders.user_id"
      }
    ],
    "filters": [
      {
        "column": "users.status",
        "operator": "=",
        "value": "active"
      },
      {
        "column": "orders.created_at",
        "operator": "between",
        "value": ["2026-01-01", "2026-12-31"]
      }
    ],
    "aggregates": [
      {
        "function": "COUNT",
        "column": "orders.id",
        "alias": "orders_count"
      },
      {
        "function": "SUM",
        "column": "orders.total",
        "alias": "total_spent"
      }
    ],
    "group_by": ["users.id", "users.name"],
    "having": [
      {
        "function": "COUNT",
        "column": "orders.id",
        "operator": ">=",
        "value": 3
      }
    ],
    "order_by": [
      {
        "column": "total_spent",
        "direction": "desc"
      }
    ],
    "limit": 10
  }'
```

## Enabling checklist

1. `config/k-framework/api.php`: `'enabled' => true`, set `'endpoint'` (POST only).
2. `config/k-framework/modules.php`: `'api_credentials_resource' => true`, then `php artisan migrate` (+ `php artisan optimize` if you cache routes, so `/resources/api_credentials` is registered).
3. Open `/resources/api_credentials` and create a credential (see view below).
4. Call the endpoint with `username`/`password` headers or Basic Auth.

## The credential view to edit (`/resources/api_credentials`)

The resource form has three tabs:

- **Credentials tab** — `enabled` toggle (disable without deleting), `username` (required), `password` + `password_confirmation` (required on create, optional on edit; min 8 with mixed case + numbers + symbols; clearing `confirm_password` requirement follows the password field), `maximum_daily_queries` (`-1` = unlimited, otherwise per-day counter `kframework_api_queries_{id}_{Y_m_d}`), `enable_only_ips` toggle.
- **Permissions tab** — `permissions` repeater, one row per table: `table_name` select (built from the current schema, minus `config/k-framework/security.php` `api_excluded_tables`) + `can_select` / `can_insert` / `can_update` / `can_delete` toggles. Rows are synced with `ModelHelper::syncModel()` on save, so removing a row revokes it.
- **IPs tab** — `ips` repeater with one `ip` input per row. Enforced only when `enable_only_ips` is on; otherwise any IP can call.

The resource table shows `enabled` dot, `username`, `maximum_daily_queries`, `created_at` (Italian format when locale is `it`), with new/print/download/pageable actions.

## Checks on every request (`ApiHelper::handle`)

In order: POST-only (`405` otherwise) → credentials present (`401`) → credential exists + `enabled` + password hash match (`401`) → IP allow-list when `enable_only_ips` (`403`) → daily quota (`429` when `maximum_daily_queries` reached) → per-table permission for the requested `operation`/`table_name` → excluded tables are never addressable, even with raw `query` + `bindings`.

Error shape is always `{'success': false, 'message': '...'}` with the HTTP code above.

## See also

- [Usage](../../../usage.md)
- [Resources](resources.md) — how `/resources/*` routes are registered (`kfw:update` / `optimize`)
- [Model Syncer](model_syncer.md) — how permissions/IPs repeaters are saved
- [Security](security.md) — `api_throttle`, `api_excluded_tables`, MIME/throttle middleware
- [DateTime helper](datetime_helper.md) — `created_at` Italian formatting used by the table