> ## Documentation Index
> Fetch the complete documentation index at: https://docs.evox.wraithesports.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Publishing

> Prepare and validate your pack with the CLI and submit it to Market moderation; learn the server-side security pipeline.

This page describes the end-to-end flow of publishing your pack to the evoX Market: which pack types can be published today, how to prepare them with the CLI, and which security gates the server runs your pack through.

## What can be published today

<Columns cols={2}>
  <Card title="Data-only packs — open">
    Ready profiles, action packs, icon packs and sound packs use the real moderation flow and can be published.
  </Card>

  <Card title="Executable plugin — closed">
    Public executable plugin upload is currently in the `503 PLUGIN_UPLOAD_NOT_READY` state. For details see <a href="/en/sdk/distribution-status">Distribution status</a>.
  </Card>
</Columns>

<Info>There is **no** separate publisher application. Anyone who is signed in can upload and submit to moderation; the only gate is moderation. Only the pack owner can submit a pack to moderation. The “Verified” badge exists only on official wraithesports content.</Info>

## Prerequisites

| Requirement | Description |
| - | - |
| evoX account | Must be signed in with Google/JWT (the only requirement) |
| Validated pack file | An `.evoxpack` file with a valid `manifest.json` |
| Passing scan | The version must have `scanStatus: passed` and `scanDigest === fileHash` |

## Step 1 — Prepare the pack with the CLI

`@evoxapp/plugin-cli` (`evox-plugin`) validates the project, produces a bundle and packages an `.evoxpack` with the `evox-archive-v1` policy.

```bash theme={}
evox-plugin validate ./my-plugin
evox-plugin build ./my-plugin
evox-plugin pack ./my-plugin
evox-plugin publish-check ./my-plugin
```

<Warning>`publish-check` is a local parity check; it does not replace moderation, a production scanner, signing or revocation. Passing it does not prove the pack is public-ready.</Warning>

For the detailed command table, see the <a href="/en/sdk/cli">Developer CLI</a> page.

<Note>The CLI `pack` output is an executable pack carrying `packageType: plugin`. Because executable upload is closed, this pack gets `503 PLUGIN_UPLOAD_NOT_READY` in Step 2 today. The data-only packs that can be published today are produced by the desktop app: <a href="/en/application/publish-your-pack">Publish your own pack</a>.</Note>

## Step 2 — Upload

The validated `.evoxpack` file is uploaded as multipart to the `POST /packages/upload` endpoint. The upload is accepted from any authenticated (signed-in) session; no separate publisher approval is needed.

The server first writes the file to secure quarantine, then scans it **within the same request**. If the scan passes, the upload response returns a draft (`draft`) version; if not, an error code is returned and no draft is created. The draft is not published yet: it is submitted separately with the **Moderation submission** step below.

## Step 3 — Server security pipeline

Every uploaded pack goes through the following gates. This flow is server-authoritative; no client field can change the result.

<Steps>
  <Step title="Quarantine">
    The file is SHA-256 hashed and written to an immutable private quarantine area. If the storage write fails, the draft is **not created** (fail-closed).
  </Step>

  <Step title="Static scan">
    Archive policy (zip bomb / path traversal / size), magic-byte match, SVG active content, secret leakage, forbidden extension and legacy identifier checks are performed.
  </Step>

  <Step title="Draft record">
    If the scan passes, the pack is recorded as a draft version atomically bound to the scan evidence (`policyVersion`, `scanDigest`, `findings`).
  </Step>

  <Step title="Moderation submission">
    The pack owner submits the version to moderation with `POST /submissions`. The body carries `termsAccepted: true` and the current submission terms version (`termsVersion`, returned by `GET /packages/upload-policy`). The client must report the draft the user accepted the terms for as `expectedVersionId` (the `version.id` from the upload response); if a newer draft was uploaded for the same product in the meantime, the submission is rejected with `409 SUBMISSION_TARGET_CHANGED`. The server verifies `scanStatus: passed` and `scanDigest === fileHash` again.
  </Step>

  <Step title="Approval">
    A moderator reviews the scan evidence and approves/rejects. Approval publishes the pack on the Market.
  </Step>
</Steps>

## Main content rejected by the scan

* **Secret leakage:** private keys, API tokens, JWTs or real credential values in config.
* **Magic-byte mismatch:** files whose extension does not match their real content type.
* **Active SVG:** SVG containing scripts, event handlers or external references.
* **Forbidden extension (data-only):** `.exe`, `.dll`, `.js`, `.bat` and similar executable/active files.
* **Legacy identifier:** use of old StreamDock UUIDs.
* **Local path leakage:** absolute file paths inside the manifest or profile.

## Upload error codes

| Code | Meaning |
| - | - |
| `UNAUTHORIZED` | No session or it could not be verified (401) |
| `PACKAGE_FILE_REQUIRED` | The pack file is missing (400) |
| `PACKAGE_STORAGE_FAILED` | The quarantine write failed (503, fail-closed) |
| `PACKAGE_SCAN_FAILED` | The pack could not be validated or did not pass the security policy (400); the `reasons` list in the response says what to fix, in the request language |
| `PACKAGE_BIND_FAILED` | The scan result could not be bound to the draft version (503, fail-closed) |
| `PLUGIN_UPLOAD_NOT_READY` | The executable plugin pipeline is closed (503) |

When uploading a new version to an existing product, these codes can also be returned:

| Code | Meaning |
| - | - |
| `MARKET_ITEM_NOT_FOUND` | The product to add the version to was not found (404) |
| `PACKAGE_OWNERSHIP_REQUIRED` | The product does not belong to you (403) |
| `PACKAGE_TYPE_MISMATCH` | The new version's pack type is not the same as the product's (409) |
| `VERSION_NOT_SEMVER` | The version number is not semver; it should be, for example, `1.0.1` (409) |
| `VERSION_NOT_INCREMENTED` | The version number is not greater than the current highest version (409) |
| `DUPLICATE_VERSION_CONTENT` | The content is identical to an existing version (409) |
| `CHANGELOG_REQUIRED` | The changelog is empty (400) |

## Moderation submission error codes

| Code | Meaning |
| - | - |
| `SUBMISSION_TARGET_NOT_FOUND` | The pack was not found (404) |
| `PACKAGE_OWNERSHIP_REQUIRED` | Only the owner can submit the pack (403) |
| `PACKAGE_VERSION_NOT_FOUND` | There is no scanned draft version to submit (404) |
| `SUBMISSION_ALREADY_PENDING` | A version of this pack is already in moderation (409) |
| `PACKAGE_FILE_REQUIRED` | The draft version has no validated pack file (409) |
| `PACKAGE_SCAN_NOT_PASSED` | A version that did not pass the scan cannot be submitted to moderation (409) |
| `SUBMISSION_TERMS_REQUIRED` | The submission terms were not accepted (400) |
| `SUBMISSION_TERMS_VERSION_MISMATCH` | The submission terms were updated; the current version must be accepted again (409) |
| `PLUGIN_ID_ALREADY_CLAIMED` | The plugin's `pluginId` belongs to another user (409) |

<Note>An executable plugin's ID (`pluginId`) is bound to the submitting user with the first approved submission that uses it; there is no separate claim step or publisher approval. Uploads and submissions using an ID bound to another user return `PLUGIN_ID_ALREADY_CLAIMED`. Because executable upload is closed, this path does not publish anything today.</Note>

<Warning>A static scan, a local sandbox smoke or a working T1 example is not public-ready on its own. The `503` gate cannot be bypassed with a client fallback, a metadata-only upload or a generic "passed" result.</Warning>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.