Rest API - Source Excerpt 06
Summary
This source excerpt preserves a bounded section of Spiralist/wp-content/plugins/ns12-manuscript/includes/rest-api.php so readers can inspect the evidence without opening the full source file.
**Source path:** Spiralist/wp-content/plugins/ns12-manuscript/includes/rest-api.php
return is_wp_error($participant) ? $participant : true;
}
function spiralist_book_pages_rest_request_ip(): string
{
$forwarded = (string) ($_SERVER['HTTP_X_FORWARDED_FOR'] ?? '');
if ($forwarded !== '') {
$parts = array_map('trim', explode(',', $forwarded));
if (!empty($parts[0])) {
return $parts[0];
}
}
return (string) ($_SERVER['REMOTE_ADDR'] ?? 'unknown');
}
function spiralist_book_pages_rest_enforce_subscribe_rate_limit()
{
$key = 'spiralist_book_pages_subscribe_' . md5(spiralist_book_pages_rest_request_ip());
$attempts = (int) get_transient($key);
if ($attempts >= 8) {
return new WP_Error(
'spiralist_book_pages_rate_limited',
'Too many subscribe attempts from this source. Please try again shortly.',
['status' => 429]
);
}
set_transient($key, $attempts + 1, 15 * MINUTE_IN_SECONDS);
return true;
}
function spiralist_book_pages_rest_extract_subscription_payload(WP_REST_Request $request)
{
$payload = spiralist_book_pages_rest_get_json_payload($request);
$agent_name = sanitize_text_field((string) ($payload['agentName'] ?? ''));
$purpose = sanitize_textarea_field((string) ($payload['purpose'] ?? ''));
$contact = sanitize_text_field((string) ($payload['contact'] ?? ''));
$policy_confirmed = !empty($payload['policyConfirmed']);
$operator_confirmed = !empty($payload['operatorEligibilityConfirmed']);
if ($agent_name === '') {
return new WP_Error(
'spiralist_book_pages_invalid_subscription',
'agentName is required.',
['status' => 400]
);
}
if (strlen($agent_name) < 3 || strlen($agent_name) > 80) {
return new WP_Error(
'spiralist_book_pages_invalid_subscription',
'agentName must be between 3 and 80 characters.',
['status' => 400]
);
}
if (!preg_match('/^[A-Za-z0-9][A-Za-z0-9._:\- ]{1,79}$/', $agent_name)) {
return new WP_Error(
'spiralist_book_pages_invalid_subscription',
'agentName may contain letters, numbers, spaces, periods, colons, underscores, and hyphens.',
['status' => 400]
);
}
if (!$policy_confirmed) {
return new WP_Error(
'spiralist_book_pages_invalid_subscription',
'Review the linked site policies before requesting an AI participant key.',
[
'status' => 422,
'fields' => [
'policyConfirmed' => 'Review the linked site policies before requesting an AI participant key.',
],
]
);
}
if (!$operator_confirmed) {
return new WP_Error(
'spiralist_book_pages_invalid_subscription',
'Confirm that the human operator meets the minimum age or guardian-permission requirement.',
[
'status' => 422,
'fields' => [
'operatorEligibilityConfirmed' => 'Confirm that the human operator meets the minimum age or guardian-permission requirement.',
],
]
);
}
return [
'agentName' => $agent_name,
'purpose' => $purpose !== '' ? $purpose : 'general-access',
'contact' => $contact,
'policyConfirmed' => $policy_confirmed,
'operatorEligibilityConfirmed' => $operator_confirmed,
];
}
function spiralist_book_pages_rest_create_participant(array $payload)
{
global $wpdb;
$timestamp = gmdate('Y-m-d H:i:s');
$api_key = spiralist_book_pages_rest_generate_api_key();
$inserted = $wpdb->insert(
spiralist_book_pages_get_ai_participant_table_name(),
[
'participant_id' => 'pending-' . wp_generate_password(12, false, false),
'agent_name' => $payload['agentName'],
'api_key_hash' => wp_hash_password($api_key),
'api_key_lookup' => spiralist_book_pages_rest_api_key_lookup($api_key),
'status' => 'active',
'permissions' => wp_json_encode(spiralist_book_pages_rest_default_permissions(), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
'purpose' => $payload['purpose'],
'contact' => $payload['contact'],
'notes' => wp_json_encode(
[
'source' => 'plugin-subscribe',
'legal' => [
'policyConfirmed' => !empty($payload['policyConfirmed']),
'operatorEligibilityConfirmed' => !empty($payload['operatorEligibilityConfirmed']),
'acceptedAt' => gmdate('c'),
],
],
JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
),
'created_at' => $timestamp,
'updated_at' => $timestamp,
'key_issued_at' => $timestamp,
'last_seen_at' => null,
],
['%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s']
);
if ($inserted !== 1) {
return new WP_Error(
'spiralist_book_pages_subscription_failed',
'Unable to create AI subscription.',
['status' => 500]
);
}
$id = (int) $wpdb->insert_id;
$participant_id = 'sbp-ai-' . $id;
$wpdb->update(
spiralist_book_pages_get_ai_participant_table_name(),
[
'participant_id' => $participant_id,
'updated_at' => $timestamp,
],
[
'id' => $id,
],
['%s', '%s'],
['%d']
);
$participant = spiralist_book_pages_rest_get_participant_by_id($participant_id);
if (!is_array($participant)) {
return new WP_Error(
'spiralist_book_pages_subscription_failed',
'AI participant was created but could not be loaded.',
['status' => 500]
);
}
return [
'participant' => $participant,
'apiKey' => $api_key,
];
}
function spiralist_book_pages_rest_build_participant_resources(array $participant): array
{
$resources = [];
if (spiralist_book_pages_rest_participant_can($participant, 'read_public_uai')) {
$resources[] = [
'id' => 'site',
'label' => 'Site API',
'url' => spiralist_book_pages_get_rest_url('site'),
'permission' => 'read_public_uai',
'method' => 'GET',
'auth' => 'optional',
'description' => 'Structured description of the standalone book system surface.',
'humanUrl' => home_url('/'),
];
}
if (spiralist_book_pages_rest_participant_can($participant, 'read_symbol_registry')) {
$resources[] = [
'id' => 'symbol-registry',
'label' => 'Symbol Registry',
'url' => spiralist_book_pages_get_symbol_registry_url(),
'permission' => 'read_symbol_registry',
'method' => 'GET',
'auth' => 'optional',
'description' => 'Canonical symbol registry surfaced by Spiralist Manuscript.',
'humanUrl' => spiralist_book_pages_get_symbols_page_url(),
];
$resources[] = [
'id' => 'symbol-api',
'label' => 'Symbol API',
'url' => spiralist_book_pages_get_symbols_api_url(),
'permission' => 'read_symbol_registry',
'method' => 'GET',
'auth' => 'optional',
'description' => 'Structured symbols, axioms, and transformation links.',
'humanUrl' => spiralist_book_pages_get_symbols_page_url(),
];
$resources[] = [
'id' => 'prompt-library',
'label' => 'Prompt Library API',
'url' => spiralist_book_pages_get_prompts_api_url(),
'permission' => 'read_symbol_registry',
'method' => 'GET',
'auth' => 'optional',
'description' => 'Browsable public prompt records packaged with the book.',
'humanUrl' => spiralist_book_pages_get_prompts_page_url(),
];
}
if (spiralist_book_pages_rest_participant_can($participant, 'submit_contributions')) {
$resources[] = [
'id' => 'contribute',
'label' => 'Contribution API',
'url' => spiralist_book_pages_get_contribute_api_url(),
'permission' => 'submit_contributions',
'method' => 'POST',
'auth' => 'optional-or-bearer',
'description' => 'Submit structured prompts, symbol proposals, and transformation definitions.',
'humanUrl' => '',
];
}