Guides
WordPress / PHP
A small plugin that keeps your API key on the server and gives your theme a safe search endpoint.
Request flow
Visitor's browser ──► your WordPress /wp-json/oms/v1/search (no key)
│
▼ Authorization: Bearer oms_live_… (key stays on the server)
OMS Course Search API POST /search
│
▼
JSON back to WordPress ──► browser renders results1. Add your key to wp-config.php
define('OMS_SEARCH_API_KEY', 'oms_live_...');2. Add the plugin
Save as wp-content/plugins/oms-course-search/oms-course-search.php and activate it. It exposes POST /wp-json/oms/v1/search to your theme, stores each visitor's conversation token in a 2-hour transient, and never sends the API key or token to the browser.
<?php
/**
* Plugin Name: OMS Course Search
* Description: Server-side proxy to the OMS Course Search API. The API key never reaches the browser.
*/
// In wp-config.php: define('OMS_SEARCH_API_KEY', 'oms_live_...');
const OMS_SEARCH_ENDPOINT = 'https://oms-api.mainconsulting.co.uk/api/public/v1/search';
function oms_search(string $query, ?string $context = null): array {
$response = wp_remote_post(OMS_SEARCH_ENDPOINT, [
'timeout' => 30,
'headers' => [
'Authorization' => 'Bearer ' . OMS_SEARCH_API_KEY,
'Content-Type' => 'application/json',
],
'body' => wp_json_encode(['query' => $query, 'context' => $context]),
]);
if (is_wp_error($response)) {
return ['ok' => false, 'status' => 0, 'error' => ['code' => 'network_error', 'message' => $response->get_error_message()]];
}
$status = wp_remote_retrieve_response_code($response);
$body = json_decode(wp_remote_retrieve_body($response), true);
if ($status !== 200) {
// Log $body['request_id'] so OMS support can trace the request.
error_log('OMS search failed: ' . $status . ' ' . ($body['request_id'] ?? '-') . ' ' . ($body['error']['code'] ?? '-'));
return ['ok' => false, 'status' => $status, 'error' => $body['error'] ?? null, 'retry_after' => wp_remote_retrieve_header($response, 'retry-after')];
}
return ['ok' => true, 'data' => $body];
}
// Browser -> your WordPress -> OMS API. Register a public REST route for your front end.
add_action('rest_api_init', function () {
register_rest_route('oms/v1', '/search', [
'methods' => 'POST',
'permission_callback' => '__return_true',
'callback' => function (WP_REST_Request $request) {
$query = sanitize_text_field((string) $request->get_param('query'));
if ($query === '' || mb_strlen($query) > 500) {
return new WP_REST_Response(['error' => 'Please enter a question (max 500 characters).'], 400);
}
// Keep the conversation token per visitor in a short-lived transient, not in the URL.
$visitor = sanitize_key((string) $request->get_param('visitor')) ?: wp_generate_uuid4();
$context = $request->get_param('new_search') ? null : get_transient('oms_ctx_' . $visitor);
$result = oms_search($query, $context ?: null);
if (!$result['ok']) {
$code = $result['status'] === 429 ? 429 : 502;
return new WP_REST_Response(['error' => 'Search is temporarily unavailable. Please try again.'], $code);
}
set_transient('oms_ctx_' . $visitor, $result['data']['context'], 2 * HOUR_IN_SECONDS);
$data = $result['data'];
unset($data['context']); // the browser never needs the token
$data['visitor'] = $visitor;
return new WP_REST_Response($data, 200);
},
]);
});3. Call it from your theme
// Browser code calls YOUR backend (e.g. the WordPress route above), never the OMS API directly.
async function searchCourses(query, { newSearch = false } = {}) {
const res = await fetch("/wp-json/oms/v1/search", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query, visitor: localStorage.getItem("omsVisitor"), new_search: newSearch }),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error ?? "Search failed");
localStorage.setItem("omsVisitor", data.visitor);
return data; // same shape as the API response, minus "context"
}Escape all text when rendering (
esc_html, esc_url in PHP, textContent in JavaScript). Treat API text as plain text, not HTML.Plain PHP (without WordPress)
<?php
// Plain PHP (no WordPress). Requires the curl extension.
$apiKey = getenv('OMS_API_KEY');
$ch = curl_init('https://oms-api.mainconsulting.co.uk/api/public/v1/search');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey, 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode(['query' => 'I need first aid training for my staff.']),
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($body, true);
if ($status !== 200) {
echo "Error {$status}: {$data['error']['code']} (request {$data['request_id']})\n";
exit(1);
}
echo $data['answer'] . "\n";
foreach ($data['courses'] as $course) {
echo "- {$course['name']} ({$course['course_url']})\n";
}