Skip to main content

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:

CredentialDescription
client_idYour application's unique identifier
client_secretYour application's secret key
caution

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'];
tip

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 CodeMeaningAction
400Invalid request parametersCheck grant_type, client_id, client_secret
401Invalid or expired credentialsVerify credentials or refresh the token
429Rate limit exceededWait 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],
]);
}
info

Plugin endpoints return 404 with a descriptive message if the required plugin is not activated on the server.


Security Best Practices

  1. Store credentials securely -- Use environment variables (getenv()) or a secrets manager. Never hardcode credentials.
  2. Use HTTPS only -- The API enforces TLS. Never send tokens over unencrypted connections.
  3. Rotate secrets regularly -- Regenerate client_secret periodically via your Feng Office admin panel.
  4. Limit scope -- Request only the scopes your application needs (read, write, admin).
  5. Cache tokens -- Store the access token and reuse it until near expiry. Avoid requesting a new token for every API call.
  6. 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"
}
FieldTypeDescription
access_tokenstringBearer token for API requests
token_typestringAlways Bearer
expires_inintegerToken lifetime in seconds
refresh_tokenstringToken used to obtain a new access token
scopestringGranted permission scopes