freeloapp / php-sdk
Modern PHP SDK for Freelo API - Project and task management
Requires
- php: ^8.1
- php-http/discovery: ^1.19
- psr/http-client: ^1.0
- psr/http-factory: ^1.0
- psr/http-message: ^1.0|^2.0
- psr/simple-cache: ^2.0|^3.0
Requires (Dev)
- guzzlehttp/guzzle: ^7.8
- nyholm/psr7: ^1.8
- php-http/guzzle7-adapter: ^1.0
- phpstan/phpstan: ^1.10
- phpunit/phpunit: ^10.5
- squizlabs/php_codesniffer: ^3.8
- symfony/cache: ^6.4|^7.0
- symfony/yaml: ^6.4|^7.0
Suggests
- guzzlehttp/guzzle: For HTTP client implementation
- nyholm/psr7: For PSR-7 message implementation
- symfony/cache: For caching support
This package is auto-updated.
Last update: 2026-03-18 16:17:29 UTC
README
Modern, lightweight PHP SDK for Freelo API.
Features
- PHP 8.1+ with strict types
- PSR-18/17/7/16 compliant (bring your own HTTP client)
- Full API coverage (projects, tasks, files, comments, time tracking, etc.)
- Dynamic credentials with server-safe per-request switching
- Low-level
call()method for arbitrary API endpoints - Automatic pagination support
- Rate limiting detection
- Typed exceptions for error handling
Installation
composer require freeloapp/php-sdk
Install an HTTP client (e.g., Guzzle):
composer require guzzlehttp/guzzle php-http/guzzle7-adapter nyholm/psr7
Quick Start
use Freelo\Sdk\Freelo; use Freelo\Sdk\Auth\ApiKeyCredentials; $freelo = new Freelo( new ApiKeyCredentials('your-api-key', 'your-email@example.com'), userAgent: 'MyApp/1.0', ); // List projects foreach ($freelo->projects()->list() as $project) { echo $project->name . "\n"; } // Create a task $task = $freelo->tasks()->create( projectId: 123, tasklistId: 456, data: ['name' => 'New task', 'priority_enum' => 'h'] );
Authentication
Get your API key from Freelo Settings (scroll to API section).
use Freelo\Sdk\Auth\ApiKeyCredentials; // Using environment variables (recommended) $credentials = new ApiKeyCredentials( apiKey: getenv('FREELO_API_KEY'), email: getenv('FREELO_EMAIL') ); $freelo = new Freelo($credentials, userAgent: 'MyApp/1.0');
Usage Examples
Projects
$projects = $freelo->projects()->list(); // List active projects $project = $freelo->projects()->get(123); // Get project by ID $freelo->projects()->archive(123); // Archive project $freelo->projects()->activate(123); // Activate archived project // Paginated results $result = $freelo->projects()->getAll(['p' => 0]); echo "Total: " . $result->getTotal();
Tasks
$tasks = $freelo->tasks()->listInTasklist(123, 456); // List tasks in tasklist $task = $freelo->tasks()->get(789); // Get task by ID $freelo->tasks()->finish(789); // Mark as complete $freelo->tasks()->activate(789); // Reopen task $freelo->tasks()->move(789, 999); // Move to another tasklist $freelo->tasks()->addComment(789, 'Comment text'); // Add comment
Time Tracking
$freelo->timeTracking()->start(taskId: 123, note: 'Working'); $report = $freelo->timeTracking()->stop(); // Manual work report $freelo->workReports()->create(123, [ 'minutes' => 120, 'date_reported' => '2024-01-15', 'note' => 'Development work', ]);
Files
$freelo->files()->uploadToTask(taskId: 123, filePath: '/path/to/file.pdf'); $freelo->files()->download(fileId: 456, destination: '/path/to/save.pdf');
Dynamic Credentials
Per-request credentials (server-safe)
Use withCredentials() to create an isolated instance with different credentials. Each derived instance has its own client and state — safe for concurrent requests in multi-tenant server applications.
// Shared base instance (e.g. in a service provider) $freelo = new Freelo(new ApiKeyCredentials( apiKey: 'default-key', email: 'default@example.com' )); // Per-request — fully isolated, concurrency-safe $userFreelo = $freelo->withCredentials(new ApiKeyCredentials( apiKey: $user->apiKey, email: $user->email )); // Optionally override userAgent per tenant $userFreelo = $freelo->withCredentials( new ApiKeyCredentials($user->apiKey, $user->email), userAgent: 'TenantApp/1.0', ); $projects = $userFreelo->projects()->list();
For simple single-user scripts, you can swap credentials in place with setCredentials():
$freelo->setCredentials(new ApiKeyCredentials( apiKey: 'new-key', email: 'new@example.com' )); // With userAgent override $freelo->setCredentials( new ApiKeyCredentials('new-key', 'new@example.com'), userAgent: 'MyApp/2.0', );
Note:
setCredentials()mutates the shared instance and is not safe for concurrent requests.
Lazy initialization
The SDK supports creating a client without credentials upfront. Credentials can be provided later via setCredentials() — validation is deferred to the first API request.
$freelo = new Freelo(); // ... later, when credentials are available $freelo->setCredentials(new ApiKeyCredentials( apiKey: $apiKey, email: $email )); $freelo->projects()->list(); // works
Low-level API Calls
Use call() to hit arbitrary API endpoints not yet covered by typed resource classes:
// GET request with query parameters $response = $freelo->call('/projects', 'GET', params: ['p' => 0]); $data = $response->json(); // POST request with body $response = $freelo->call('/projects/123/tasklists/456/tasks', 'POST', data: [ 'name' => 'New task', 'priority_enum' => 'h', ]); // Also available on derived instances $userFreelo = $freelo->withCredentials($credentials); $response = $userFreelo->call('/users/me', 'GET');
Available Resources
| Resource | Method | Description |
|---|---|---|
| Projects | projects() |
Project management |
| Tasks | tasks() |
Task management |
| Tasklists | tasklists() |
Tasklist management |
| Comments | comments() |
Comment management |
| Files | files() |
File upload/download |
| Task Labels | taskLabels() |
Task label management |
| Project Labels | projectLabels() |
Project label management |
| Subtasks | subtasks() |
Subtask management |
| Time Tracking | timeTracking() |
Time tracking |
| Work Reports | workReports() |
Work report management |
| Users | users() |
User management |
| Notifications | notifications() |
Notifications |
| Events | events() |
Activity events |
| Notes | notes() |
Note management |
| Search | search() |
Full-text search |
| Custom Fields | customFields() |
Custom fields |
| Invoices | invoices() |
Invoice management |
Pagination
use Freelo\Sdk\Http\Paginator; // Auto-paginate through all results foreach (Paginator::fetchAll(fn($p) => $freelo->projects()->getAll(['p' => $p])) as $project) { echo $project->name . "\n"; } // Manual pagination $page = $freelo->tasks()->getAll(['p' => 0]); echo "Page {$page->getPage()} of " . ceil($page->getTotal() / 20); if ($page->hasNextPage()) { $nextPage = $freelo->tasks()->getAll(['p' => $page->getNextPage()]); }
Error Handling
use Freelo\Sdk\Exception\{ ApiException, AuthenticationException, NotFoundException, RateLimitException, ValidationException }; try { $project = $freelo->projects()->get(999); } catch (NotFoundException $e) { // Resource not found (404) } catch (AuthenticationException $e) { // Invalid credentials (401) } catch (RateLimitException $e) { // Rate limited (429) - retry after $e->getRetryAfter() seconds } catch (ValidationException $e) { // Invalid input (422) } catch (ApiException $e) { // Other API errors }
Rate Limiting
The API allows 25 requests/minute. The SDK tracks limits automatically:
$limiter = $freelo->getClient()->getRateLimiter(); if ($limiter->isLimitExceeded()) { sleep($limiter->getSecondsUntilReset()); }
Custom HTTP Client
use GuzzleHttp\Client; use Http\Adapter\Guzzle7\Client as GuzzleAdapter; $httpClient = new GuzzleAdapter(new Client(['timeout' => 30])); $freelo = new Freelo($credentials, httpClient: $httpClient);
Caching (Optional)
use Symfony\Component\Cache\Psr16Cache; use Symfony\Component\Cache\Adapter\FilesystemAdapter; $cache = new Psr16Cache(new FilesystemAdapter('freelo', 3600)); $freelo = new Freelo($credentials, cache: $cache);
Development
composer install # Install dependencies composer test # Run tests composer phpstan # Static analysis composer check # Run all checks
Requirements
- PHP 8.1+
- PSR-18 HTTP Client (e.g., Guzzle)
- PSR-17 HTTP Factories (e.g., nyholm/psr7)
License
MIT License. See LICENSE.