Software Distribution REST API
Normative reference for software package issuance, update checking, one-time token redemption, and external manual releases
Overview
The Software Distribution REST API governs the issuance, authorization, verification, and redemption of versioned software packages in WooNooW. It provides endpoints for automated updaters, bootstrap installers, reinstall operations, and manual customer downloads. A protected package request is not the activation bootstrap itself: the exact website identity must already have an active license activation.
Base URL: https://your-store.com/wp-json/woonoow/v1
Package resolution enforces server-side release selection and SHA-256 artifact integrity. Eligible local releases use short-lived, single-use bearer tokens; eligible external releases use a separate gated manual-link flow and never mint updater tokens. Clients never request or specify target releases, destination filenames, filesystem paths, download URLs, storage drivers, or checksum overrides.
Mental model and architecture
Software distribution in WooNooW strictly separates the product catalog hierarchy from release delivery streams.
graph TD
A[Parent WooCommerce Product & Software Slug] --> B{Licensing Policy}
B -->|Protected| C[License Key & Active Website Identity]
B -->|Explicitly Public| D[Unlicensed Public Access]
C --> E[Parent Release Stream]
D --> E
E --> F[Server Evaluates Published Releases]
F --> G[Exact Immutable Artifact]
G -->|Local Vault Package| H[Server-Selected Authorized Package & Single-Use Token]
G -->|External Manual HTTPS| I[Manual-Only Flag & Gated External Link]
Key architectural invariants
- Parent product release stream: Releases belong strictly to the parent WooCommerce product identified by its unique
slug. A product variation represents entitlement and pricing metadata (or a source download artifact), never an independent release stream. - Fail-closed product classification: Product resolution searches only published WooCommerce
productposts.- If zero products match the slug, resolution fails with
404 product_not_found. - If duplicate published products share a slug, resolution fails closed with
409 software_slug_ambiguous. - Products are license-protected by default. A product is treated as public only when its meta
_woonoow_licensing_enabledis explicitly set to string'no'. Missing, unconfigured, or invalid licensing metadata is treated as protected.
- If zero products match the slug, resolution fails with
- Active website identity pair: Protected products require an active license and an active installation binding:
Both components are mandatory. Omitting either returns
400 missing_identity; supplying a complete identity that has no active activation for the license returns403 domain_not_activated. See Website Identity for identity construction rules. - Server-side release selection: The server determines the single best eligible release based on publication status, version comparison, physical artifact integrity, and license entitlements. Clients never supply a desired target version, release ID, artifact ID, storage key, or checksum.
- Short-lived bearer tokens: Local package delivery produces an ephemeral, single-use bearer token. Plaintext tokens are never stored in the database; only their SHA-256 hashes are persisted with strict server-side bindings.
Endpoints summary
Canonical package request (POST /software/package)
POST /software/package is the primary endpoint for retrieving software packages for installation, reinstallation, and updates.
Allowed request fields
The server enforces strict JSON schema validation. Any unknown or unsupported parameter causes immediate rejection with HTTP 400 unsupported_parameter.
Field constraints and validation rules
- Strict rejection of unknown fields: Parameters such as
version,release_id,download_url,storage_driver,storage_key,file_path,artifact_id, orchecksumare rejected immediately. - Install and Reinstall: The
current_versionfield must not be sent. If present (even empty or null), the server returns400 current_version_not_allowed. - Update: The
current_versionfield is mandatory and must be non-empty. If omitted, the server returns400 current_version_required. - Protected products: When licensing is enabled for the product, omitting
license_key,site_url, orinstallation_idreturns400 missing_identityor403 license_required.
Server release selection algorithm
The server queries published releases for the parent product ordered by:
- Current primary release flag (
is_current DESC) - Effective publication timestamp (
COALESCE(published_at, release_date) DESC) - Version record ID (
id DESC)
A candidate release must satisfy all of the following:
- It belongs to the resolved parent product.
- Its status is
published(neverdraftorwithdrawn). - For
update, its version compares newer thancurrent_versionusing PHPversion_compare(). - Its artifact is valid: a local package must resolve to a supported protected filesystem artifact with matching SHA-256; an external release must contain a valid manual HTTPS record and complete SHA-256 metadata.
- For protected products, the license entitlement rules authorize the release for the requested purpose via Entitlement Evaluation.
Outcome of candidate evaluation:
- Candidate authorized: The newest release satisfying all constraints is issued.
- No candidates match (
404 no_eligible_release): Returned when no published releases exist for the product, or when forpurpose=updateno published release is newer thancurrent_version. - Candidates rejected (
403 no_eligible_release): Returned when published candidate releases exist, but every candidate failed release authorization (e.g., published after update cutoff without history access) or artifact validation. When a candidate fails entitlement authorization, the specific authorization error (e.g.release_not_entitled) is returned.
Request examples
1. Install request (new deployment)
current_version is omitted. The server returns the first artifact-valid, entitled release in its authoritative current/publication ordering.
2. Reinstall request (repair or replace an installation)
current_version is omitted. The server selects the first published release authorized by the same entitlement/cutoff rules as every other purpose. Because the client does not identify its installed version, reinstall does not promise to return those exact previous bytes.
3. Update request (upgrade to newer version)
current_version is mandatory. The server selects the first authorized published release that compares newer under PHP version_compare(). Semantic Versioning (for example, 1.4.2) is recommended for predictable publisher/client conventions, but core does not apply a strict SemVer 2.0 regex.
4. Explicitly public product request (unlicensed)
For products configured with licensing disabled, identity and license keys are omitted.
Successful responses
Local vault package response (automated delivery)
When the eligible release is hosted in the verified local vault, the server creates a single-use bearer token and returns an automated download URL.
External release response (manual download only)
When the eligible release is configured with an external storage driver, automated updater token generation is prohibited. The response signals manual handling and supplies a gated external route:
Update compatibility check (GET|POST /software/check)
/software/check is a compatibility endpoint designed for WordPress updater libraries (such as Plugin Update Checker). It evaluates whether an update is available and whether the client is eligible to receive it.
Request fields
Available vs. eligible semantics
The response makes a strict distinction between releases that exist and releases this client is authorized to download:
available: A boolean indicating whether a newer published, artifact-valid release exists in the release stream, regardless of this license's entitlement status.eligible: A boolean indicating whether the client's license and website identity are authorized to receive that newer release.update_available: A legacy boolean field deliberately aligned to equaleligible. It istrueonly when an authorized delivery path exists.latest_version: Reportseligible_versionwhen the license is entitled; otherwise falls back toavailable_version; otherwise the current or configured version.eligibility_reason: Explains why an available release cannot be downloaded (e.g.,release_not_entitled).
Representative response (update available, license not entitled)
In this scenario, version 2.0.0 exists, but the customer's update access cutoff expired before that version was published:
If the client were eligible for a local package, download_url, token_expires_at, and token_binding_version would be populated with single-use bearer token details identical to POST /software/package.
Download token redemption (GET /software/download)
Clients redeem short-lived bearer tokens via GET /software/download.
Security and token binding model
- Bearer secret: The
tokenquery parameter carries a random 64-character bearer string. - Hash-only database storage: The store database holds only the SHA-256 hash (
token_hash) of the token. Plaintext tokens cannot be recovered from server storage. - Strict identity and release binding (Token Binding v1): Every token binds
binding_version, protected/public policy, product, release/version, artifact ID, artifact SHA-256, purpose, creation time, and expiry. A protected token additionally binds the exact license, activation ID, activation generation/revision, canonical identity key, installation UUID, and normalized domain. A public-product token has no license or activation identity and cannot be repurposed for another product or release.
Revalidation and atomic one-time claim
When a token is presented for redemption:
- Hash lookup: The server hashes the provided token and loads the matching row.
- Pre-claim revalidation: Core validates token expiration, invalidation flags, and binding completeness.
- Artifact pre-check: Core resolves the authoritative supported artifact and verifies its immutable metadata before entering the transaction. A managed local release must remain in the private vault outside every checked public root with matching size and SHA-256. Legacy
wc_downloadablecompatibility is accepted only when the resolved file is already outside all checked public roots; addon drivers must provide their own valid metadata contract. - Authenticated encrypted preflight (before transaction): For encrypted-at-rest releases, core executes a bounded-memory preflight verification (64 KB chunk hash loop with a discard sink) before opening the database transaction or acquiring InnoDB row locks (
FOR UPDATE). Because Sodium Secretstream authenticates strictly when pulling ciphertext chunks, this preflight verifies the database key, Poly1305 chunk MACs, final tag, and uncorrupted plaintext SHA-256 before any token or license row is locked. Moving this outside the transaction prevents long row locks on large packages. If preflight fails, the request fails closed immediately without consuming the token or streaming broken plaintext with a valid Content-Length. - Short transactional lock: Within a short database transaction, core locks the download, release, and—when protected—license and activation rows (
FOR UPDATE). It confirms:- The product is published and software distribution is enabled.
- The licensing policy has not changed since token issuance (
license_policy_changed). - The license remains valid and entitled to the release.
- The bound activation remains active with identical generation and revision (
activation_binding_changed). - Physical file stat stability (mtime and size). While file stat checks detect modification, standard filesystem TOCTOU boundaries remain a hosting consideration if external processes modify files between stat and stream.
- Atomic claim: Core executes an atomic conditional SQL update:
If the update affects 0 rows, the claim fails with
403 token_consumed. - Transaction commit before delivery: The transaction commits before any file streaming or extension filter begins.
- Post-claim transfer failure: Once claimed, the token is permanently consumed. If a network interruption, connection reset, or post-claim addon error occurs, the token is not reopened. The client must issue a new request to
/software/packageor/software/checkto obtain a fresh token.
Method restrictions and non-consuming behaviors
To protect clients and automated update agents from accidental token consumption:
- Only
tokenparameter allowed: Any additional or unrecognized query parameter returns HTTP400 download_override_rejectedwithout consuming the token. - HEAD requests are rejected: HTTP
HEADreturns405 head_not_supported,Allow: GET, andAccept-Ranges: nonewithout claiming the token. - Byte range requests are rejected: Any request containing a
Rangeheader returns416 range_not_supportedandAccept-Ranges: nonewithout claiming the token.
Delivery response headers
When streaming a local package, the server emits:
Client rules
- Do not cache download URLs: Download URLs are single-use and expire quickly (default TTL is 5 minutes, configurable from 1 to 60 minutes). Clients must request package URLs immediately prior to download.
- Always verify SHA-256: Treat package metadata
file_sha256as the expected value, compute SHA-256 over all downloaded bytes, and compare before extraction or execution. WhenX-Package-Sha256is present, it must also match the metadata; the header does not replace byte verification.
Public changelog (GET /software/changelog)
The changelog route exposes version history for a software product.
Query parameters
Behavior and visibility
- Published releases only: Draft releases (
release_status = 'draft') and withdrawn releases (release_status = 'withdrawn') are excluded. - Unauthenticated access: This route is publicly accessible without licensing or identity parameters.
- Module state note: In the current implementation,
/software/changelogis registered as a public informational route; it remains accessible even if the software distribution module is toggled off and does not consume rate-limit tokens.
Response example (version list)
Gated external manual releases
For products hosted on external storage (such as third-party vendor platforms or external mirrors), WooNooW provides a gated entitlement route:
Query parameters
Authorization rules
- Protected products: The endpoint requires full identity validation (
installation_id+ normalized domain matching an active activation) and verifies that the license entitlement allows the release for the specifiedpurpose. - Public products: Protected against arbitrary public scraping: access requires either administrative privileges (
manage_woocommerce) or a logged-in customer account verified to have purchased the product viawc_customer_bought_product(). - Operational security boundary: Once the external HTTPS URL is returned to the client, byte transfer, link revocation, access control, and CDN/storage ACLs are outside WooNooW core's control. External links are manual-only and are strictly prohibited from the automated
/software/downloadtoken route (updater_external_link_prohibited).
Successful response
Release administration API
These routes are for the WooNooW store, release tooling, and CI automation—not for distributed client software. Every route in this section requires a WordPress user with manage_woocommerce.
For an interactive browser/SPA request, authenticate with the normal WordPress login cookie and X-WP-Nonce. For remote automation, use a WordPress-supported authenticated mechanism such as an Application Password over HTTPS. Keep administrator credentials out of the software package and customer installations.
Administration routes
Direct multipart ZIP upload (POST /software/products/{product_id}/versions)
The primary and default release publication mechanism is a direct multipart form-data upload. Operators and deployment pipelines submit the .zip archive directly to the release endpoint. WooNooW stages the package in private storage, computes hashes, and publishes the release immediately into the secure vault without touching the public Media Library.
Request parameters (multipart/form-data)
Backend ingestion & integrity guarantees
- No WooCommerce Artifact ID Required: Direct upload does not require creating a WooCommerce downloadable file entry or looking up an
artifact_download_id. The server automatically generates a unique UUIDv4 identifier (artifact_download_id) upon ingestion. - Automatic SHA-256 Checksum, Filename, & Vaulting: The server streams the staged package into
{vaultDir}/products/{productId}/{targetFilename}, calculating the SHA-256 hash and byte size in-flight. - Master Client File Unaffected, PHP Temp Consumed: The operator's master archive on their local computer is completely untouched. In the server environment, PHP places the upload in temporary storage (
$_FILES['file']['tmp_name']). WooNooW verifiesis_uploaded_file(), stages it into private vault staging ({vaultDir}/staging/{uuid}.zip) viamove_uploaded_file()(which safely consumes PHP's temporary file), applies safe permissions (0640), verifies generic ZIP integrity, copies exclusively into the product vault, and unlinks the staging file immediately. - Generic ZIP Structure & Integrity Validation: WooNooW validates ZIP archive integrity by verifying magic byte signatures (
PK\x03\x04,PK\x05\x06,PK\x07\x08) and testing archive readability viaZipArchive::open(). The validator supports any generic opaque ZIP packages (WordPress plugins, themes, desktop applications, CLI binaries, or arbitrary archives). It does not inspect or enforce internal WordPress plugin headers or require specific PHP files inside the archive. - Staging Cleanup & Ingested Artifact Retention on Failure: Temporary files in
{vaultDir}/staging/are always removed immediately after vault ingestion or upon failure. If release publication fails (whether pre-draft on duplicate versions or mid-transaction rollbacks), the ingested artifact in{vaultDir}/products/{productId}/is preserved in private vault storage rather than eagerly deleted. This eliminates concurrent deletion races where simultaneous uploads could destroy files in flight, guarantees safe draft recovery, and allows subsequent retries to reuse the verified package safely. Error payloads returnstorage_keyandrecovery_actionwithout exposing absolute server filesystem paths. - Signature: Optional, Nullable, Still NOT Input Supported: The database schema includes
signature(text, nullable),signing_key_id(varchar, nullable), andsigned_at(datetime, nullable), and API responses include these fields (defaulting tonull). However, neither the direct multipart upload endpoint nor the Admin UI currently provides input fields or supports passing cryptographic signatures or signing keys during version publication. Releases are published unsigned with null signature metadata.
cURL request example
Success response (HTTP 201 Created)
Error responses
Legacy publication & migration (POST /software/products/{product_id}/versions with JSON)
When migrating an existing release package already uploaded to WordPress Media or configured as a native WooCommerce downloadable file, submit an application/json payload referencing the WooCommerce artifact_download_id:
Core resolves the authoritative WooCommerce artifact, copies and hashes the bytes into the product-scoped vault, writes a durable non-current draft, detaches matching native WooCommerce downloads across the parent/variation scope, safely removes the exact public source when authorized, revalidates the vault artifact, and transactionally publishes it. set_current defaults to true; send false to publish without changing the current release.
Shared references block explicit deletion
A public Media/uploads source without deletion consent fails with 409 source_still_public and remains untouched. If deletion is authorized (delete_public_source: true), WooNooW audits known references. If another WooCommerce product, variation, software release, or WordPress post still references the public file, deletion is strictly blocked:
The public file remains intact, detached downloads are restored, and a recoverable draft is preserved. Operators can resolve the references and call republish on the saved draft.
Publish an external manual release
For packages hosted on external HTTPS infrastructure (vendor CDNs, external mirrors), submit an application/json payload with storage_driver: "external" and the pre-computed SHA-256 hash:
Publication detaches the matching native WooCommerce link so it cannot bypass the WooNooW gate. The resulting release is always manual-only: it never receives /software/download updater tokens.
Resolving legacy artifact IDs
Resolving a WooCommerce download_id is required only for the legacy migration workflow and external links. Direct ZIP uploads do not use this step.
An authenticated product response exposes IDs at:
Read downloads[].id or variations[].downloads[].id from the JSON response. Do not log the Application Password or reuse administrator credentials in distributed client software.
Withdraw, recover, and edit
Withdraw a published release:
Republish a withdrawn release or recover a draft:
A withdrawn release preserves its original effective publication cutoff when republished, so republishing cannot extend customer entitlement. Previously invalidated tokens remain invalid. A draft receives its first publication timestamp only after successful publication.
PUT /versions/{version_id} currently requires the existing version value and may update changelog/current state. It cannot change a published version string, artifact ID, or checksum; publish a new version instead.
Private Artifact Library API
The Private Artifact Library decouples package ingestion from release publication. Store administrators can upload private packages in advance, audit byte size and SHA-256 integrity, inspect which releases currently reference each package, and publish new versions by selecting a previously uploaded artifact without re-uploading bytes.
Packages in the private artifact library are strictly scoped to the parent product's private vault storage and are never exposed in the WordPress Media Library or accessible via public webroot URLs.
List private artifacts
Query parameters:
Response (HTTP 200):
Upload package to private library
Response (HTTP 201 Created):
Publish release from private library artifact
When publishing a software release from a previously uploaded private artifact, provide the server-authoritative artifact_id:
Security & Integrity Guarantees:
- Server-authoritative binding: The server resolves
storage_key,file_size, andfile_sha256strictly from the durable database record. Any client-providedstorage_keyis ignored. - Strict product scope enforcement: Artifacts uploaded for Product A cannot be selected or published for Product B. Cross-product requests are rejected with HTTP
403 cross_product_artifact_prohibited. - Immutable hash binding: Before creating the release, the server verifies that the physical archive in vault storage matches the recorded SHA-256 hash and size. If the file on disk has been corrupted or altered, publication fails closed with HTTP
409 artifact_integrity_failed. - Template independence: The original uploaded package filename (e.g.
MyPlugin-v2.2.0-rc1.zip) is preserved in the library record, while the client-facing release binary filename is rendered from the product's filename template (e.g.myplugin_v_2.2.0.zip). - Reusable across versions: A single library package can be selected for multiple release versions (e.g. initial release and metadata-only hotfix updates). Each version maintains its own immutable release binding.
Software Artifact Deletion & Storage Management API (P0)
The Artifact Deletion API gives store administrators complete, audited control over physical file removal. It supports deleting unused packages, historical release packages, current release packages, and shared physical packages.
Core Security & Architecture Rules:
- Authentication & Capabilities: All endpoints require
manage_woocommerce. Product-scoped routes additionally requirecurrent_user_can('edit_post', $product_id). For browser session requests, a valid WordPress REST nonce (X-WP-Nonce) is mandatory. - Target Identification by ID Only: Deletion targets must be identified strictly by server-authoritative database integer IDs or UUIDs (
artifact_ids,release_ids). Request payloads that pass raw filesystem paths, vault directories, driver names, or bucket names are strictly rejected with HTTP400 invalid_parameters. - Zero Server Path Leakage & Storage Key Retention: All responses pass through recursive sanitization. Server-internal absolute paths (
physical_path,vault_root,storage_root,absolute_path,staging_path,local_path) and private storage identity objects are stripped before returning data to clients. Relativestorage_keyis preserved in baseline artifact and version publication success responses per existing API contract. - Two-Phase State Machine & Idempotency: Deletion executes in two decoupled phases via database journal tables (
woonoow_software_deletion_jobsandwoonoow_software_deletion_items). The interactive API commits in-transaction closures and enqueues asynchronous physical unlinks, returning HTTP202 Acceptedwhile unfinished. Repeated execution requests with the sameidempotency_keyreturn existing job details. Idempotency survives preview expiry; a committed operation remains replayable even after the preview transient has expired. - Publish Prohibition: Artifacts in
delete_pending,delete_failed, ordeletedstate are barred from being bound or published in new releases (409 artifact_not_available). - Roadmap Scope: P0 (implemented; automated verification recorded TESTING_CHECKLIST; live host acceptance pending) covers manual deletion, storage usage summary, and impact preview. P1 deduplication, P2 scheduled retention, and cloud storage addons remain future milestones.
1. Storage usage summary
Query parameters:
Response (HTTP 200 OK):
Measurement confidence:
known: all physical objects have verified exactstored_size_bytesestimated: some objects fallback to legacy catalogfile_sizemixed: some objects have unknown sizes where neither stored bytes nor file size are available
2. Generate deletion impact preview
Before executing physical deletion, clients must request an impact preview. The preview computes the closure of all affected releases and physical files, returning a short-lived preview_token (valid for 15 minutes).
Validation constraints:
- At least one target (
artifact_idsorrelease_ids) is required. - Combined target count must not exceed 100 per request (
max_targets_exceeded). - Unknown parameters (such as
path,file,force,driver) are rejected with400 invalid_parameters. - Cross-product targets: If target physical artifacts are shared across multiple products, the actor must have
edit_postpermission on all affected products. If permission is missing on any affected product, preview fails with403 cross_product_permission_denied. Cross-product deletion is allowed with all affected products consent/permissions (cross_product_impact), not an absolute prohibition. (Note: for library artifact selection during release publicationPOST /versions, selecting a file uploaded to a different product is rejected with403 cross_product_artifact_prohibited).
Response (HTTP 200 OK):
3. Execute confirmed artifact deletion
Execution semantics:
- Stale preview protection: If targets, release references, or file metadata changed between preview creation and execution, returns
409 deletion_preview_stale. - Tiered acknowledgements: All strings in
required_acknowledgementsmust be provided inacknowledgements. If any acknowledgement is missing, returns400 deletion_confirmation_required. - Idempotency survives preview expiry: Repeated execution with matching
idempotency_key, product, and preview token returns the committed operation without re-initiating unlinks, even after the preview transient has expired or been consumed. - In-Transaction phase: Sets target artifacts to
delete_pending, marks referencing published releaseswithdrawn, clearsis_currentif applicable, and transactionally invalidates outstanding unclaimed tokens with reasonartifact_deleted. - Asynchronous worker execution: Enqueues physical file unlinks to background workers. Returns HTTP
202 Acceptedwhile status ispendingorrunning, or HTTP200 OKif immediately completed.
Response (HTTP 202 Accepted):
4. Get deletion operation status
Response (HTTP 200 OK):
5. Retry failed deletion operation
Re-enqueues items in failed status or pending status with outcome = 'retryable_failure' within an existing deletion job for worker processing. Resets item status to pending, clears error codes and outcomes, resets attempts to 0, and dispatches background workers immediately. Background workers apply exponential backoff (min(300, 2^attempts * 30) seconds) for retryable failures.
Response (HTTP 202 Accepted):
Storage adapter extension hooks contract
Core natively implements local and local_encrypted drivers. Addon storage drivers (such as future Cloudflare R2 or Amazon S3 integrations) must adhere to the following filter contracts:
woonoow/software/storage_capabilities: Addons must declare their inspection and deletion capabilities:woonoow/software/stat_managed_artifact: Must return a validated snapshot array containingidentity(driver,backend,key),identity_hash, booleanexists,stored_size_bytes(integer ≥ 0 when existing; null when absent),absence_evidence(required whenexists === false), andcapabilities. Core strictly rejects raw HTTP 4xx/5xx status codes and failure keywords (timeout,curl_error,permission_denied, etc.) with500 storage_stat_failed. Addon drivers must normalize authorized 404 responses to named strings:not_foundorconfirmed_absentonly.woonoow/software/delete_managed_artifact: Must return an outcome array confirming physical absence:outcome: Exactly one of'deleted','already_missing','retryable_failure','permanent_failure'.removed_bytes: Integer bytes removed (must be 0 foralready_missingand delete markers).error_code: Redacted machine-readable error string if failed.
Verified Post-Stat Identity Absence: For both deleted and already_missing outcomes from addon drivers, core performs a mandatory post-stat verification (SoftwareStorage::stat_managed_artifact) to confirm physical absence (exists === false), matching backend identity, and version. Delete markers and missing files report 0 for removed_bytes (delete markers do not count as released bytes). If the file remains or only a delete marker was created, core returns retryable_failure with error code file_remains_after_delete or delete_marker_only.
No Core Cloud Support: Cloudflare R2 and Amazon S3 are not implemented in WooNooW core. All cloud functionality belongs in future dedicated addons.
Product configuration and storage checks
POST /software/products/{product_id}/config accepts slug, filename_template, storage_driver, licensing_enabled, and software_enabled. A licensing/software policy change invalidates outstanding tokens for that product. The default filename template is {slug}_v_{version}.zip; clients must not derive a target URL or version from it.
Call GET /software/storage/status before local publication. The endpoint reports effective_driver (local or local_encrypted), storage_mode (private_vault or encrypted_at_rest), and detailed diagnostics for both storage types. When storage_mode: "encrypted_at_rest" is active, encrypted.status: "secure" confirms that authenticated encryption (Sodium Secretstream XChaCha20-Poly1305) is initialized with a secure server-managed key. filesystem_status: "secure" means PHP verified the reported filesystem boundaries; overall status: "host_verification_required" remains until the operator audits web-server aliases, CDN/cache copies, and the old source URL. See Software Distribution Configuration for the operational checklist.
POST /artifacts/validate returns HTTP 200 with valid: false when a filename does not match the effective template. Treat the response body—not only the HTTP status—as the validation result.
Rate limiting architecture
Package/check issuance and gated external-link requests use an application-level rate limiter to reduce brute-force abuse and polling loops.
- Storage mechanism: Backed by WordPress transients with a 1-minute TTL (
woonoow_software_rate_{hash}). - Bucket segmentation:
- Protected requests: Keyed by authoritative database
license_id(license|{license_id}). - Public products: Keyed by product ID and hashed IP (
public|{product_id}|{ip_hash}). - Unknown products or failed license lookups: Keyed by hashed client IP (
unknown|{ip_hash}). - Hashed-IP guard: An overarching client IP bucket capped at
max(10, min(500, limit * 5)).
- Protected requests: Keyed by authoritative database
- Limits: Derived from the software module setting
rate_limit(clamped between 1 and 100 requests per minute; default is10). - Best-effort boundary: The limiter uses a non-atomic
get_transient→ increment →set_transientsequence. Under high concurrency, requests may race past the nominal threshold. It is an abuse mitigation measure, not a cryptographic or host security boundary. Production deployments must enforce edge/reverse-proxy rate limits (e.g., Nginxlimit_req, Cloudflare, or AWS WAF). - Exceeded response: When a bucket limit is reached, the server returns HTTP
429 rate_limitedwith error data containingretry_after: 60.
Integrity and cryptographic signing
- Managed-local integrity: A publishable local release carries a complete 64-character lowercase hexadecimal SHA-256 value. Core calculates and verifies the actual bytes during vault staging and before token claim/redemption.
- External integrity metadata: External publication requires the administrator to supply a valid SHA-256 value, but core does not fetch or continuously verify third-party bytes. Once the external URL is disclosed, provider behavior and byte delivery are outside core; the receiving workflow should verify the downloaded bytes against the published hash.
- Signing metadata status: The API returns
signature,signing_key_id, andsigned_atfields in package and update responses for forward compatibility. These fields are optional and nullable in database records and API responses (defaulting tonull). WooNooW core does not require cryptographic signatures, and neither the direct upload endpoint nor the Admin UI currently provides input fields or support for entering signatures or signing keys. If non-null signature data is present via custom backend filters, it must not be treated as an authoritative verification mechanism without external validation.
Explicit system limitations
Client developers and integrators must be aware of the following deliberate architectural constraints:
- No byte-range resumption: Partial downloads, multi-threaded download managers, and
Rangeheaders are explicitly unsupported and return HTTP416 range_not_supported. - No HEAD probing: Automated tools must not send
HEADrequests to verify token validity;HEADreturns HTTP405 head_not_supported. - No client version selection: Clients cannot request an arbitrary target version (for example, "give me 1.8.0"). The server selects a release based on status, entitlement, configured ordering, and—for update requests—PHP
version_compare()against the installed version. - No provider integration in core: WooNooW core has no native Cloudflare R2, AWS S3, Google Drive, or OneDrive driver. Core supports managed local delivery and unmanaged HTTPS external links for manual download only.
- Addon extension boundary: A storage addon may extend accepted drivers and artifact metadata through
woonoow/software/valid_storage_driversandwoonoow/software/resolve_artifact_metadata, then prepare delivery throughwoonoow/software/prepare_download. The preparation hook runs strictly after the core atomic token claim commits. Adding a driver name alone does not implement validation or delivery, and no addon may bypass product/release authorization, license checks, or token consumption.
Principal error codes
Software routes currently use two error envelopes: standard WordPress REST errors expose the machine code as code, while several compatibility/controller responses expose it as error. Normalize with const errorCode = body.code ?? body.error, then branch on that stable value plus the HTTP status. Never match localized message text.
Errors are grouped below by lifecycle phase: Software Preflight Errors, Release Candidate & Authorization Errors, and Download Token & Redemption Errors.
1. Software preflight errors
Preflight errors are evaluated before candidate release resolution begins. They indicate invalid input syntax, rate limits, disabled modules, or failure of the requesting identity and base license to pass prerequisite checks.
2. Release candidate & authorization errors
Evaluated during server release selection (resolve_eligible_release()) and entitlement evaluation (LicenseEntitlements::authorize_release()).
3. Download token & redemption errors
Evaluated when redeeming a one-time bearer token via GET /software/download?token=....
4. Storage & artifact deletion errors
Evaluated during artifact inspection, deletion preview, execution, status, retry, and release publication.
Related documentation
- Entitlements & Policy Evaluation — How license usage, update cutoffs, and historical access are evaluated.
- Website Identity Standard — Rules for persistent installation UUIDs and domain normalization.
- Software Distribution Configuration — Store administrator settings, vault paths, and rate limit configuration.
- Software Updates Integration Guide — Implementing client updaters, WordPress update filters, and background update runners.
Last updated Sep 8, 2026