This is the data reference for On Hold content API 1.0.0, manifest format 2. Use Your first mod for complete runnable files. The smaller JSON examples below are section fragments: insert them at the indicated place in a full pack. An example describing a reference does not supply every referenced definition.
Folder and document contract
your-pack/
mod.json
content.json
achievements.json
README.md
LICENSE.txt
preview.png
voice/
art/
mod.json is required. A pack containing no gameplay can omit unused content sections. The loader treats missing content.json as empty content and missing trophies as an empty list. Use UTF-8 JSON with normal quoted keys and finite numbers. No comments, trailing commas, functions or executable expressions belong in these files.
Runtime JSON and image files have an 8,000,000-byte per-file ceiling; audio files have a 32,000,000-byte ceiling. A shared archive has at most 10,000 files and a 512,000,000-byte expanded ceiling. Shared JSON arrays cannot exceed 10,000 entries and nesting cannot exceed 24 levels. Field-specific limits below are often much lower. Use these as maximum supported boundaries, not recommended design targets; a small readable pack is easier to validate and maintain.
Asset references are relative portable paths. Images belong under art/ and are PNG, JPEG or WebP. Recordings belong under voice/ and are MP3, OGG or WAV. Avoid accented/nonportable filename characters; the accepted path character set is letters, digits, underscores, periods, slashes, spaces and hyphens. Paths cannot contain traversal segments, backslashes, drive letters, absolute paths, NULs or external URLs. The folder is not a way to load remote assets.
Manifest fields
| Field | Contract |
|---|---|
schemaVersion |
2 for new packs. Legacy format 1 is adapted with defaults. |
id |
Stable 3–40-character lowercase mod ID: letters, digits, _, -. |
name, author, description |
Player-facing metadata. Supply useful plain text. |
version |
Three numeric parts, such as 1.3.0. Keep identity and version separate. |
gameVersion |
*, exact version, or whitespace-separated comparisons. |
contentApi |
Same range syntax, evaluated against API 1.0.0. |
contentTypes |
Array from the accepted types below; Studio infers additions when saving. |
modes |
Nonempty array of supported mode IDs. |
dependencies |
At most 64 {id, version} records. Legacy string IDs mean any version. |
conflicts |
At most 64 mod IDs that cannot be selected with this pack. |
workshopId |
Publishing item identity retained by Studio. Do not copy it into a new derivative. |
workshop |
Remembered publication title, description, tags/authorTags and visibility. Let Studio maintain it. |
Accepted content types are data, scenarios, achievements, audio, translations, office, challenges, campaigns, characters and nightshift. Types are declarations, not a script permission system. Actual capability detection inspects the content. A pack declaring capabilities: ["cosmetic"] cannot also supply gameplay data.
Mode IDs are career, nightshift, challenge, campaign and multiplayer. If absent in legacy/defaulted data, the first four are used; multiplayer needs explicit support. A first-party starter can intentionally include all five for reusable calls/presentation, while an authored campaign should restrict modes as appropriate.
Range examples: 1.0.0, >=1.0.0, >=1.0.0 <2.0.0, *. Unsupported examples: ^1.0.0, ~1.0.0, 1.x, >=1.0.0 || <0.9.0. Dependencies use the same explicit comparison syntax. Never depend on your own ID. Missing dependencies, cycles and declared conflicts block the selection.
IDs and references
Most authored definition IDs use 1–64 letters, digits, underscores or hyphens; case-sensitive authored identity should remain consistent. Reserved keys __proto__, constructor and prototype are rejected in JSON objects. Voice keys must be unique within a pack. Individual content arrays cannot contain duplicate definition IDs.
The loader owns the namespace. A local HELP article becomes your-pack:HELP; a local CUSTOM_FIX action is stored in the action registry with its normalized namespaced identity. A scenario uses local references to owned articles/actions. Stock IDs remain stock IDs. For another pack's content, declare the dependency and use its namespaced reference such as common-guides:ROUTER_HELP; the actual merged definition must exist. A declared namespace is not permission to reference a nonexistent item.
Published IDs are part of saves, choices, trophies and recordings. Change text freely when appropriate, but keep identity stable for the same definition. A fundamentally different campaign or incompatible story should use a deliberate new identity/version strategy rather than silently reusing another story's saved keys.
The sixteen content editors
Studio exposes the following sixteen sections. Fifteen are keys of content.json; Achievements is the separate achievements.json document. Manifest metadata is an additional editor, not a seventeenth content capability.
1. Scenarios: scenarios
Array, at most 1,000 definitions. Required ordinary-call fields are id, unique voice, nonempty label, category, numeric weight (0–100), integer minDay (1–10,000), mood, opener pool, resolution pool and solutions. Categories are Internet, Billing, Account, Technical, TV, Mobile, Security and Other. Moods are calm, annoyed, angry.
openers is a nonempty array of dialogue strings. Optional clarify, details and resolveLines are arrays of nonempty strings; an ordinary non-special/non-graph call needs resolution lines. Text entries have a 16,000-character bound. An opener containing {detail} needs a nonempty details pool, and each expanded variant has its own recording target.
flags is an array of string flags attached through supported game behavior; use established flags rather than expecting a new arbitrary string to create mechanics. characterId references a recurring character. noVoice is an existing call presentation convention; it does not supply any new behavior.
solutions is an array of objects with quality: "best" | "good" | "ok", optional kb, action and diagnostic cause. Ordinary calls need a nonempty solution list. A referenced article/action must exist. cause must identify a cause from that call's diagnosis.
diagnosis needs prompt, desc and nonempty causes. Each cause has a unique id, nonempty reveal, and positive weight up to 100. Weight selects a cause; it is not a percentage that has to sum to 100. Test each cause and ensure a usable solution exists.
responseMenu.options supports up to 16 choices with id, nonempty agent text, optional caller reply array, and stable lineId/replyLineIds. Keep response IDs short and portable; 1–40 characters also satisfies the runtime merge path. This ordinary response menu is distinct from the bounded branching dialogue graph and replaces the ordinary KB/CRM option route while the unresolved problem is stated. Include at least one fitting option with fit: true; selecting it resolves with its quality (best, good, ok, default good). An option without fit: true is a wrong response, applies mood (default −12) and does not resolve. Keep responseMenu absent when teaching a combined knowledge/CRM solution.
lineIds maps dialogue pool names to token arrays of identical length. Tokens are unique within a pool. lineIdCounter is the designer's monotonic allocation counter. The same token 01 can exist in clarification and resolution because their full recording IDs have different prefixes. Move tokens together with text; never recycle a deleted published token.
special can select an existing handler: scam, fraud, prank, wrong, anonymous, social_eng, dd_associate, glitch, misdial, silence, the_regular. This reuses built-in behavior; it does not register a new handler. A dialogue graph can replace ordinary solution routing and supports at most 64 nodes. See Stories and campaigns for its complete contract.
2. Knowledge: kbArticles
Array, at most 1,000 definitions. Each article needs id, nonempty title and a nonempty steps array of nonempty text. keywords supplies searchable terms. Optional agentLine supplies the spoken explanation; give a useful explicit line instead of relying on the engine's fallback step selection. Link a call solution with "kb": "YOUR_ARTICLE".
3. CRM: crmActions
Object keyed by local action ID, each containing nonempty label and optional desc:
{"CUSTOM_RESET":{"label":"Reset the connection","desc":"A reset for the authored call."}}
This example is the value of crmActions. It makes a named action available to authored solutions. Arbitrary scripts, callbacks and invented account mutation hooks are unsupported. Use bounded story effects for owned variables, mail and other supported authored outcomes.
4. Lead packs: leadPacks
Array, at most 1,000. Each definition needs id, name, finite price (0–1,000,000). count is an integer 1–200 and defaults to 10. Optional bias is interested, busy, hostile or lonely; omit it for no bias. switchers and shady are booleans; desc explains the offering. These add purchasable prospect packs to the existing outbound system. A new lead pack does not replace every cold-call mechanic or add a scripted service.
5. Mail: emails
Object keyed by integer day strings 1–10,000. Each day has up to 100 entries containing from, subj, body; optional stable id is useful when adding choices. Mail appends to existing career mail rather than replacing the entire inbox. Plain authored text remains literal. A choices array follows the bounded mail-choice rules, with up to eight choices and once-only saved responses.
{"1":[{"id":"welcome","from":"Team lead","subj":"A new guide","body":"Search WikiDesk for our router checklist."}]}
This is an emails section fragment. Campaign-day mail and triggered rule mail are separate supported locations using the same choice behavior.
6. Career days: dayConfigs
Object targeting only stock authored days "1" through "5". Partial overrides can set label, intro, calls (integer 1–100), minGapSec and callTimeTarget (1–3,600 seconds), and specials referencing existing stock/local scenarios. Existing fields fill the remainder. Validation warns because profile order determines the winning override.
This does not replace day-six-onward procedural career scheduling. Use a separate campaign when designing an entire custom sequence. Experience configurations have different tighter timing bounds; do not apply the stock-day 3,600-second ceiling to them.
7. Career modifiers: dayModifiers
Array, at most 1,000. Required id, name, effects; optional weight (0–100), positive integer minDay, banner, icon, and email. Defaults at merge are weight 1, minimum day 8, banner=name. Effects tune approved coefficients; outage scheduling hooks remain engine-owned. Modifier mail supports bounded choices.
| Modifier | Bounds/type |
|---|---|
payMul, resolvePayMul, griftRiskMul, strikeMul, sipPowerMul, stressMul |
0–10 |
ringGapMul, callsMul |
0.1–10 |
moodDelta |
−100–100 |
surrealChanceAdd |
−1–1 |
stressFloor |
0–100 |
allVip, allIrate, breakDisabled, focusDisabled |
Boolean |
Only these names are supported. A multiplier of 1 preserves its existing coefficient; a zero or extreme value can make a challenge trivial or impossible. Apply changes sparingly and test their interaction with equipment.
8. Local trophies: achievements.json
An array, or a legacy object with an achievements array. Each trophy has unique id, nonempty name, optional desc/hidden, and trigger. Supported triggers:
totalResolved: integercountfrom 1 to 1,000,000.resolveScenario:scenarioreferencing a supplied or stock call; optionalminCsatfrom 0 to 5.event:oniscallEnded,dayStart,dayEnd,choiceormailChoice; optionalwhenuses bounded conditions.
Trophies belong to the pack and remain local. They cannot award an arbitrary Steam achievement. Scratch and private modded multiplayer suppress awards, and custom experience rules use owned local trophies.
9–11. Presentation
translations is an array of at most 32 locale descriptors containing locale, optional native name, direction, font, and strings/phrases/dialogue dictionaries. Each dictionary has at most 10,000 keys. voiceOverrides is an array of {lineId, actor, locale?, file} records. Its section validator accepts 20,000 records, but the shared 10,000-entry JSON-array limit is the effective ceiling for one array. Split very large libraries into packs instead of relying on the larger section number.
office is an object containing palette, wallpaper, monitorWallpaper, skins, posters. See Languages, audio and office packs for the exact surface names, actors, filenames, lookup order and bounds.
12–16. Experiences and recurring people
challenges, campaigns, nightshiftWaves, nightshiftMemos and characters are arrays with at most 100 definitions each. Challenges need a config and objectives; campaigns need one to 100 authored days; waves reuse the shift configuration; memos use the modifier whitelist; characters need stable id and name.
Detailed graph/rule/state and campaign fields are in Stories and campaigns. Full challenge timing, starting equipment, objective metrics, wave/memo selection and multiplayer constraints are in Challenges, Night Shift and co-op.
Validation is the final authority
Unknown fields do not become features merely because JSON parses. Many plain metadata fields are retained for forward compatibility, while recognized runtime fields still follow their contract. Prefer the supported definitions and inspect warnings/errors. The main/native validator additionally checks the actual files, budgets and safe paths; the editor's structural pass cannot prove that a recording will decode or that a story is enjoyable.
When changing a reference, validate the whole pack rather than just that section. Then export and re-import a release copy. This catches files or private recovery data that do not belong in the shared archive. Preserve stable IDs, update the pack version for releases, document migration changes, and test resume against a copy of the previous version before claiming compatibility.
