Authentication Guide
The Feng Office API uses OAuth 2.0 Client Credentials for server-to-server authentication. All API requests (except token generation) require a valid Bearer token in the Authorization header.
Prerequisites
Before you begin, obtain your API credentials from your Feng Office administrator:
| Credential | Description |
|---|---|
client_id | Your application's unique identifier |
client_secret | Your application's secret key |
Never expose your client_secret in client-side code, public repositories, or logs. Store credentials in environment variables or a secure vault.
Token Endpoint
POST https://api.fengoffice.com/rest/api/v2/oauth/token
Content-Type: application/json
Step 1: Obtain an Access Token
Using PHP cURL
<?php
$tokenUrl = 'https://api.fengoffice.com/rest/api/v2/oauth/token';
$clientId = getenv('FENG_CLIENT_ID');
$clientSecret = getenv('FENG_CLIENT_SECRET');
$ch = curl_init($tokenUrl);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Accept: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'grant_type' => 'client_credentials',
'client_id' => $clientId,
'client_secret' => $clientSecret,
'scope' => 'read write',
]),
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
throw new RuntimeException("Token request failed with HTTP {$httpCode}: {$response}");
}
$tokenData = json_decode($response, true);
$accessToken = $tokenData['data']['access_token'];
$expiresIn = $tokenData['data']['expires_in'];
echo "Access token obtained, expires in {$expiresIn} seconds.\n";
Using PHP Guzzle
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
$client = new Client([
'base_uri' => 'https://api.fengoffice.com/rest/api/v2/',
'timeout' => 10,
]);
try {
$response = $client->post('oauth/token', [
'json' => [
'grant_type' => 'client_credentials',
'client_id' => getenv('FENG_CLIENT_ID'),
'client_secret' => getenv('FENG_CLIENT_SECRET'),
'scope' => 'read write',
],
]);
$tokenData = json_decode($response->getBody(), true);
$accessToken = $tokenData['data']['access_token'];
$refreshToken = $tokenData['data']['refresh_token'];
$expiresIn = $tokenData['data']['expires_in'];
echo "Access token obtained, expires in {$expiresIn} seconds.\n";
} catch (ClientException $e) {
$errorBody = $e->getResponse()->getBody()->getContents();
throw new RuntimeException("Authentication failed: {$errorBody}");
}
Step 2: Use the Token in API Requests
Include the access token as a Bearer token in the Authorization header of every request:
<?php
// cURL example
$ch = curl_init('https://api.fengoffice.com/rest/api/v2/tasks?status=open');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $accessToken,
'Accept: application/json',
],
]);
$tasks = json_decode(curl_exec($ch), true);
curl_close($ch);
<?php
// Guzzle example
$response = $client->get('tasks', [
'headers' => [
'Authorization' => 'Bearer ' . $accessToken,
'Accept' => 'application/json',
],
'query' => ['status' => 'open'],
]);
$tasks = json_decode($response->getBody(), true);
Step 3: Refresh an Expired Token
Access tokens expire after the duration specified in expires_in (in seconds). Use the refresh token to obtain a new access token without re-sending credentials:
<?php
// cURL
$ch = curl_init('https://api.fengoffice.com/rest/api/v2/oauth/token');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Accept: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'grant_type' => 'refresh_token',
'refresh_token' => $refreshToken,
'client_id' => getenv('FENG_CLIENT_ID'),
'client_secret' => getenv('FENG_CLIENT_SECRET'),
]),
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
$accessToken = $response['data']['access_token'];
<?php
// Guzzle
$response = $client->post('oauth/token', [
'json' => [
'grant_type' => 'refresh_token',
'refresh_token' => $refreshToken,
'client_id' => getenv('FENG_CLIENT_ID'),
'client_secret' => getenv('FENG_CLIENT_SECRET'),
],
]);
$tokenData = json_decode($response->getBody(), true);
$accessToken = $tokenData['data']['access_token'];
Implement proactive token refresh by checking expires_in and refreshing a few minutes before expiry, rather than waiting for a 401 response.
Error Handling
| HTTP Code | Meaning | Action |
|---|---|---|
400 | Invalid request parameters | Check grant_type, client_id, client_secret |
401 | Invalid or expired credentials | Verify credentials or refresh the token |
429 | Rate limit exceeded | Wait and retry after the indicated period |
Handling 401 in Middleware
<?php
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Middleware;
use GuzzleHttp\HandlerStack;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
$stack = HandlerStack::create();
// Auto-refresh middleware
$stack->push(Middleware::retry(
function ($retries, RequestInterface $request, ?ResponseInterface $response) {
// Retry once on 401
return $retries === 0
&& $response !== null
&& $response->getStatusCode() === 401;
},
function () {
return 0; // No delay
}
));
$client = new Client([
'base_uri' => 'https://api.fengoffice.com/rest/api/v2/',
'handler' => $stack,
]);
Discovering Active Plugins
Before calling plugin-specific endpoints (e.g., /plugins/billing/expenses), verify the required plugin is active:
<?php
$response = $client->get('plugins', [
'headers' => ['Authorization' => 'Bearer ' . $accessToken],
'query' => ['is_activated' => true],
]);
$plugins = json_decode($response->getBody(), true);
$activePluginNames = array_column($plugins['data'], 'name');
if (in_array('expenses', $activePluginNames)) {
// Safe to call billing endpoints
$expenses = $client->get('plugins/billing/expenses', [
'headers' => ['Authorization' => 'Bearer ' . $accessToken],
]);
}
Plugin endpoints return 404 with a descriptive message if the required plugin is not activated on the server.
Security Best Practices
- Store credentials securely -- Use environment variables (
getenv()) or a secrets manager. Never hardcode credentials. - Use HTTPS only -- The API enforces TLS. Never send tokens over unencrypted connections.
- Rotate secrets regularly -- Regenerate
client_secretperiodically via your Feng Office admin panel. - Limit scope -- Request only the scopes your application needs (
read,write,admin). - Cache tokens -- Store the access token and reuse it until near expiry. Avoid requesting a new token for every API call.
- Log responsibly -- Never log access tokens or client secrets. Log only non-sensitive metadata (HTTP status, request path).
Token Response Reference
{
"error": false,
"data": {
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4...",
"scope": "read write"
},
"message": "Token issued"
}
| Field | Type | Description |
|---|---|---|
access_token | string | Bearer token for API requests |
token_type | string | Always Bearer |
expires_in | integer | Token lifetime in seconds |
refresh_token | string | Token used to obtain a new access token |
scope | string | Granted permission scopes |