Only the top-level `message` is processed; other fields and nested messages are preserved. A message, when supplied, must be a string. Encoding requires complete groups of three ASCII digits, each between `000` and `255`. The resulting bytes must form valid UTF-8 text for JSON storage. Valid UTF-8 Unicode and JSON-escaped control characters are supported; arbitrary non-UTF-8 binary requires a different format.

Malformed encoded messages return HTTP `422` with `invalid_encoded_message`, and no submission is created. A missing message with an encoding hint, a non-string message, or an unknown mode also returns `422`. The existing request body-size limit applies before decoding. HTML entities such as `&#x20;` are not decoded by this feature; use the byte group `032` for a space.

| Status | Meaning |
| --- | --- |
| `201` | Submission saved |
| `400` | Malformed JSON, malformed form field names or percent escapes, or invalid UTF-8 |
| `401` | Missing or incorrect bearer token when authentication is configured |
| `404` | Unknown route |
| `405` | Method not allowed |
| `413` | Body exceeds the configured byte limit |
| `415` | Unsupported media type, including multipart forms |
| `422` | Empty body, JSON scalar, invalid encoded message/type/mode, or exceeded form field count or nesting limits |
| `500` | Unexpected server or storage failure |

## Configuration

Set environment variables before starting PHP. Defaults are in `config/app.php`.

| Variable | Default | Purpose |
| --- | --- | --- |
| `STORAGE_PATH` | `<project>/storage/submissions` | Optional absolute directory override; unset or empty uses the original project folder |
| `MAX_BODY_BYTES` | `1048576` | Positive maximum request size in bytes (1 MiB by default) |
| `API_TOKEN` | Unset | Optional bearer token required for POST requests when set |

Include `Authorization: Bearer <your-token>` in authenticated POST requests:

```powershell
curl.exe -i http://127.0.0.1:8000/index.php -H "Authorization: Bearer $env:API_TOKEN" -H "Content-Type: application/json" --data-binary "@payload.json"
```

An ordinary HTML form cannot supply that header; use an HTTP client or JavaScript when token authentication is enabled. The default setup accepts unauthenticated submissions.

Align your production web server's request-size limit and PHP's `post_max_size` with the application limit. URL-encoded forms also remain subject to PHP's configured input variable and nesting limits.

In production `php.ini`, set `display_errors = Off` and `log_errors = On`. Disabling displayed errors also prevents PHP errors raised before the application starts from adding HTML to API responses.

## Project structure

```text
public/index.php                 Entry point
bootstrap.php                    Builds the application and its dependencies
autoload.php                     Local class autoloader
config/app.php                   Environment-backed configuration
routes.php                       Route definitions
src/Core/                        Application and router
src/Http/                        Request, response, body parsing, HTTP errors
src/Middleware/                  Middleware interface and bearer authentication
src/Controllers/                 Submission controller
src/Services/                    Message processing before persistence
src/Storage/                     Repository interface and file implementation
src/Support/                     Logger, JSON helpers, secure random IDs
storage/submissions/             Default records directory for every deployment
tests/run.php                    Integration suite
```

Add routes in `routes.php`, request filters under `src/Middleware`, and business logic under `src/Controllers`. Replace `FileSubmissionRepository` with another implementation of `SubmissionRepository` to change persistence.

For example, add this inside the existing route-registration function in `routes.php`, then request `index.php?route=hello`:

```php
$router->add('GET', '/hello', static function (Request $request) {
    return Response::json([
        'success' => true,
        'request_id' => $request->id(),
        'data' => ['message' => 'Hello'],
    ]);
});
```

Each record gets a random server-generated filename; submitted fields cannot choose a path. Storage writes a temporary file in the destination directory, then renames it into place. A successful response is sent only after storage completes. Authorization headers and client IP headers are not included in the saved record; any sensitive values deliberately placed in the request body are saved as submitted.

The project does not provide a retrieval endpoint, storage quotas, retention cleanup, or rate limiting. Configure those operational controls as needed. Atomic file publication prevents readers from seeing a partially written record; it does not guarantee durability after power loss.