360 API Sync
Synchronizes clinic and doctor data from the PR360 API into WordPress custom post types for the 360 medical site network.
Install
This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.
Install straight from GitHub with WP-CLI:
wp plugin install https://github.com/kazimiralvis/360-api-sync/archive/refs/heads/main.zipStage 2 responsibility note: API Sync remains the upstream transport and canonical writer. When 360 Platform Core is active, relationship persistence and entity/sync lifecycle events use the Core contract while legacy compatibility writes remain intact.
Repository: https://github.com/KazimirAlvis/360-api-sync
Plugin Overview
360 API Sync synchronizes clinic and doctor content from the PR360 API into existing WordPress clinic and doctor CPTs. It supports remote API mode and mock JSON mode.
Installation
- Place plugin in
wp-content/plugins/360-api-sync. - Activate 360 API Sync.
- Open 360 API Sync → Settings.
- Configure API values or enable mock mode.
- Run manual sync.
Configuration
Settings page fields:
- API Base URL
- API Key
- GitHub Token (optional, for private-repo plugin updates)
- Site Slug
- Enable Mock API
- Dry Run Mode
- Manual Sync button
Mock file:
mock-data/clinics.json
API Sync Architecture
- Base URL:
https://cmltutizsixpurslzfzl.supabase.co/functions/v1
- Endpoint:
GET /sync?site_slug={site_slug}
- Auth headers:
x-api-key: API_KEY- temporary fallback:
Authorization: Bearer API_KEY(compatibility only)
Remote Trigger Endpoint (for Lovable)
- WordPress REST route:
POST /wp-json/pr360/v1/sync-pull
- Authentication:
- Configure a separate Inbound Sync Secret. The outbound API key is not the preferred inbound credential.
- Preferred signed requests send
x-360-timestamp, a uniquex-360-request-id, andx-360-signature. - The signature is lowercase hex HMAC-SHA256 of
timestamp + "\n" + request_id + "\n" + sha256(raw_body)using the inbound secret. - Timestamps have a five-minute window and request IDs are single-use during that window.
- Temporary compatibility:
x-api-keymay contain the inbound secret. If no inbound secret has been configured, the outbound API key is accepted as a deprecated fallback.
- Behavior:
- Triggers
Cron::run_sync( 'remote' ). - Returns HTTP
200on sync success,500on sync failure,401on invalid key,503when key is not configured. - Returns
409while another sync owns the lock and429when legacy requests exceed the basic rate limit.
- Triggers
- State options:
360_api_last_sync360_api_sync_last_run_result
API response shape:
site_slugconditionclinics[]- each clinic includes
doctors[]
- each clinic includes
Clinic & Doctor Mapping
Clinic identity key: organization_id
- Updates post + metadata for clinic SEO/schema keys including:
_cpt360_clinic_bio_cpt360_clinic_phone_clinic_website_urlclinic_addressesclinic_infoclinic_reviewsgoogle_place_id
Doctor identity key: doctor_id (with doctor_slug used for URL/temporary compatibility)
- Updates post + metadata including:
doctor_namedoctor_titledoctor_bioclinic_id(array of clinic post IDs)
Image Handling
- Clinic logos and doctor photos are downloaded and inserted as Media Library attachments.
- Images are stored in:
/uploads/360-clinics/{organization_id}//uploads/360-doctors/{organization_id}/
- Canonical keys used by theme:
_clinic_logo_id_doctor_photo_id
- Duplicate imports are skipped when source URL has not changed.
Cron Sync
- Event hook:
360_api_sync_event - Interval: every 6 hours
- Order: fetch payload once, sync clinics, then sync nested doctors
- Payload safety guard: sync aborts if clinic count is empty or exceeds 2000
- Sync log page: 360 API Sync → Sync Log
Synchronization safety
- Every cron, manual, and remote run must acquire the same non-autoloaded WordPress option-row lock.
- Acquisition uses atomic
add_option(). The lock has a unique owner token and a 15-minute expiry. Active runs renew the expiry with an owner-matched SQL heartbeat and invalidate the WordPress option cache after direct SQL changes. Stale recovery and release use compare-and-delete SQL so one run cannot modify or delete another run's lock. - Release occurs in a
finallypath. Current lock context and expiry are visible on the settings page and in the stored last-run result. - Missing-doctor reconciliation is disabled unless the API explicitly returns boolean
snapshot_complete: true. The current API/mock contract does not provide that marker, so missing doctors are preserved. Explicit inactive records are still drafted. - The upstream API must add
snapshot_complete: trueonly for validated, complete authoritative snapshots before missing-record reconciliation can run.
Run status and cursor rules
Runs are classified as success, warning, retryable_entity_failure, or fatal_failure. Only success and genuinely non-blocking warning runs may advance 360_api_last_sync. Post persistence, required canonical metadata, relationship, reconciliation, transport, database, and lock failures are retryable or fatal and prevent advancement. Dry runs never advance it.
Malformed individual clinic/doctor rows are rejected and reported as data-quality warnings; they do not prevent otherwise valid entities from synchronizing. Optional media is security-validated independently: unsafe URLs, SVG, oversized files, and invalid raster content are rejected and reported, while the canonical entity persists without a new image (and an existing usable image is retained). Optional media failure alone does not block the global cursor.
Every attempted sync has a public run ID recorded in its result, lock diagnostics, and sync log. This identifier is separate from the private lock-owner token.
Field presence semantics
For API-managed addresses, reviews, clinic information, phone, website, assessment ID, and clinic/doctor images:
- omitted field: preserve stored data;
- present empty field: clear canonical and compatibility values;
- present populated field: update canonical and compatibility values.
An explicit empty image also clears the API-managed featured image reference. Existing compatibility aliases remain during this stage.
Remote media security
- Downloads use
wp_safe_remote_get()with redirect limits and WordPress URL validation. - Private/unsafe targets are rejected by the safe HTTP API.
- Downloads are capped at 10 MB and validated from actual file content.
- Only JPEG, PNG, GIF, and WebP are accepted.
- SVG imports are disabled because the plugin has no SVG sanitizer.
Dry Run Mode
- Enable Dry Run Mode in plugin settings to simulate a sync.
- When enabled, the plugin:
- fetches and parses API payloads
- computes create/update counts
- does not write posts, meta, images, or advance
360_api_last_sync
- Useful for validating API changes safely before live writes.
Temporary Records Handling
- Clinics without
organization_idand doctors withoutdoctor_slugare created as temporary records. - Temporary records are marked with
_360_is_temporary = 1and a deterministic_360_temp_key. - On later syncs, when permanent IDs become available, temporary records are upgraded instead of creating duplicates.
- Missing IDs are treated as warnings (not fatal sync errors) unless a record is missing both identity and display fields.
Publish / Draft Lifecycle Sync
- Clinics and doctors are drafted when API marks them inactive via
is_active,published, orstatus. - Doctors missing from the latest API payload are drafted during cleanup.
- Temporary clinic records missing from the latest payload are drafted during cleanup.
- Manual sync runs in full-reconciliation mode (cursor bypass) to apply lifecycle updates immediately.
Database log table:
wp_360_api_sync_log- columns:
id,sync_time,context,clinics_processed,doctors_processed,images_imported,errors
GitHub Update System
This plugin uses a manifest-based GitHub updater:
- https://github.com/KazimirAlvis/360-api-sync
- https://raw.githubusercontent.com/KazimirAlvis/360-api-sync/main/plugin-manifest.json
Release workflow:
- Update version in
360-api-sync.php,README.md,CHANGELOG.md, andplugin-manifest.json. - Push to
main. - In WordPress admin, click Check for Plugin Updates on the plugin settings page.
No token UI or wp-config.php token is required when manifest + ZIP URLs are publicly accessible.
Development
- Version:
1.5.5 - Changelog:
CHANGELOG.md - Main bootstrap:
360-api-sync.php - Lint check:
find . -name "*.php" -print0 | xargs -0 -n1 php -l