Перейти к содержимому

Rotation JSON

Updated October 18, 2026

The JSON is the complete description of an advanced rotation: everything the KRC text has, plus spell IDs, slot flags and profile settings. The builder shows it on the Text and JSON page, and KeyRota accepts it as a file (New — From file…, Import). The format is called keyrota-rotation, version 1. There is a JSON Schema to check it.

Example file#

The shortest useful rotation: Rupture at 5+ combo points when it isn't on the target, otherwise Mutilate.

minimal.json
{
  "format": "keyrota-rotation",
  "version": 1,
  "title": "Assassination: minimum",
  "specID": 259,
  "resources": ["PlayerHP", "TargetHP", "Energy", "Combo_Points"],
  "auras": [{"n": 1, "alias": "Rupture_DoT", "label": "Rupture on target [1943]", "spell": 1943}],
  "slots": [
    {"n": 2, "alias": "Rupture", "label": "Rupture", "spell": 1943},
    {"n": 4, "alias": "Mutilate", "label": "Mutilate", "spell": 1329}
  ],
  "rules": [
    {
      "use": "Rupture",
      "name": "Rupture",
      "when": [{"all": ["Combo_Points >= 70", "Rupture_DoT.down"], "comment": "5+ combo points, no bleed"}]
    },
    {"use": "Mutilate", "name": "Mutilate"}
  ]
}

Everything the file leaves out takes its default: slots show With a target and have no flags, the enemy counter is 8 yards, enemies in combat only. A rule without when always holds.

Top-level fields#

FieldTypeRequiredDefaultWhat it is
formatstringyes—Always "keyrota-rotation".
versionintegeryes—Always 1. A newer one is refused: "update KeyRota".
titlestringno""Rotation name: the first text line, the name in the log. No double quotes.
specIDinteger 0–100000no0WoW specialization, 0 = any. Needed for the Better Assistant profile.
ignoreMainIcontrue / falsenotrueOn: press only by the rules. Off: when no rule matches, press the game's suggestion.
enemiesobjectno{"range": 8, "combatOnly": true}The enemy counter for the enemies condition.
notesstring or list of stringsno[]Description: # lines at the top of the text.
resourceslist of strings and nullno[]Names of resource1…N in order.
auraslistno[]Auras buff1…12.
slotslistno[]Slots slot1…12.
ruleslistno[]Rules in priority order.
footerstring or list of stringsno[]Comments after the last rule.

Any other field is an error, "unexpected field": typos are not swallowed. So don't add "$schema" to the file; attach the schema in your editor or validator.

Almost everywhere null may stand for a value; it means the same as leaving the field out.

format and version#

"format": "keyrota-rotation" and "version": 1 are required. If version is above 1, KeyRota refuses: "version 2 is newer than this KeyRota understands: update it". Without version: "version 1 required".

title#

A string without double quotes ". Line breaks become spaces. In KRC text it is rotation "…". A good place for the version: "Assassination v1.3".

specID#

The game's specialization ID. It decides the rotation's resources (when created in the builder) and the class and specialization in the Better Assistant profile. 0 means any specialization: the rotation works, but the Better Assistant profile won't build ("Choose a specialization: the Better Assistant profile needs it").

ClassSpecializationspecIDSpecialization resources (resource3…)
WarriorArms71Rage
WarriorFury72Rage
WarriorProtection73Rage
PaladinHoly65Mana, Holy_Power
PaladinProtection66Mana, Holy_Power
PaladinRetribution70Mana, Holy_Power
HunterBeast Mastery253Focus
HunterMarksmanship254Focus
HunterSurvival255Focus
RogueAssassination259Energy, Combo_Points
RogueOutlaw260Energy, Combo_Points
RogueSubtlety261Energy, Combo_Points
PriestDiscipline256Mana, Insanity
PriestHoly257Mana, Insanity
PriestShadow258Mana, Insanity
Death KnightBlood250Runes, Runic_Power
Death KnightFrost251Runes, Runic_Power
Death KnightUnholy252Runes, Runic_Power
ShamanElemental262Mana, Maelstrom
ShamanEnhancement263Mana, Maelstrom
ShamanRestoration264Mana, Maelstrom
MageArcane62Mana, Arcane_Charges
MageFire63Mana, Arcane_Charges
MageFrost64Mana, Arcane_Charges
WarlockAffliction265Mana, Soul_Shards
WarlockDemonology266Mana, Soul_Shards
WarlockDestruction267Mana, Soul_Shards
MonkBrewmaster268Energy, Stagger
MonkMistweaver270Mana
MonkWindwalker269Energy, Chi
DruidBalance102Mana, Astral_Power
DruidFeral103Energy, Combo_Points
DruidGuardian104Rage
DruidRestoration105Mana, Astral_Power
Demon HunterHavoc577Fury
Demon HunterVengeance581Fury
Demon HunterDevourer1480Fury
EvokerDevastation1467Mana, Essence
EvokerPreservation1468Mana, Essence
EvokerAugmentation1473Mana, Essence

ignoreMainIcon#

true (default): KeyRota presses only by the rules; when none matches, it waits. false: it then presses the game's suggestion, like the Blizzard profile. The suggestion is the last resort after all the rules; it never overtakes a rule.

enemies#

JSON
"enemies": {"range": 8, "combatOnly": true}
FieldTypeDefaultWhat it is
rangeinteger 1–1008The enemy counter radius in yards.
combatOnlytrue / falsetrueCount only enemies already in combat.

The enemies condition reads this counter (see Enemies nearby). In the builder these are "Enemy counter radius, yards" and "Only enemies in combat" on the General page. The values also go into the Better Assistant profile.

notes and footer#

The rotation's description (notes) and the comments after the last rule (footer). A list of strings, or one string with \n. An empty string "" is a blank line in the text. Blank lines at the edges are dropped.

JSON
"notes": ["Based on a guide and top logs, patch 12.1", "", "v1.1: Envenom earlier on 4+ targets"]

In KRC text these are # … lines before the declarations. They don't affect how the rotation works.

resources#

Resource names in order: the first item is resource1, the second resource2, and so on. The number decides what the resource is; the name is only how you write it in conditions:

  • resource1 is the player's health, resource2 the target's health;
  • resource3 and on are the specialization's resources in the order of the specID table: for a rogue resource3 is energy, resource4 combo points.
JSON
"resources": ["PlayerHP", "TargetHP", "Energy", "Combo_Points"]

Use the standard names from the table: the builder shows them in plain words ("Player health", "Energy"), and other people and AI agents understand the conditions right away. null skips a number: ["PlayerHP", null, "Energy"] declares only resource1 and resource3. An undeclared resource can't be read: "resource2 is not declared".

All resources are percentages of the maximum, 0–100. How to turn combo points, energy and runes into percentages: Resources in Conditions.

auras#

Auras are your buffs and your debuffs on the target. Rotation aura n is aura n in the Better Assistant list.

FieldTypeRequiredDefaultWhat it is
ninteger 1–12yes—Aura number (buffN). Each number once.
aliasstringnofrom labelThe name in conditions. See Names.
labelstringno""Display name. No double quotes ". Handy with the ID in brackets: "Garrote on target [703]".
spellinteger 0–100000000no0The aura's Wowhead ID. 0 = not set: the aura won't be in the Better Assistant profile and will be unknown.
notestringno""A comment at the end of the declaration line.
JSON
"auras": [
  {"n": 1, "alias": "Garrote_DoT", "label": "Garrote on target [703]", "spell": 703},
  {"n": 2, "alias": "Rupture_DoT", "label": "Rupture on target [1943]", "spell": 1943}
]

The order in the list doesn't matter, n does. Numbers may have gaps, but for the Better Assistant profile the auras with IDs must go in a row from the first.

slots#

Slots are the rotation's buttons. Rotation slot n is slot n in Better Assistant.

FieldTypeRequiredDefaultWhat it is
ninteger 1–12yes—Slot number (slotN).
aliasstringnofrom labelThe name in rules and conditions.
labelstringno""The spell's name. No double quotes ".
spellinteger 0–100000000no0The Wowhead spell ID. Better Assistant uses it to put the spell into the slot.
notestringno""A comment at the end of the declaration line.
show"target" / "combat"no"target"When Better Assistant shows the slot.
manualtrue / falsenofalseFlag M.
channeltrue / falsenofalseFlag C.
offGcdtrue / falsenofalseFlag O.
queuetrue / falsenofalseFlag Q, only with offGcd.

show#

  • "target": the slot is shown when you have a target. True for almost every attack.
  • "combat": the slot is shown in combat even without a target. For healing and defensives: Crimson Vial must work when there's no target.

A slot is ready only when Better Assistant shows it. A "target" slot without a target isn't ready, and its rules are skipped.

Slot flags#

Flags say how to press a slot once its rule is chosen. In the builder they are the How to press switches.

FlagsIn JSONHow it's pressedExample
noneall falseIn the queue window at the end of the GCD: the spell goes off as soon as the GCD ends.Mutilate, Envenom
M"manual": trueNever. The slot's rules are skipped. Pressing its key yourself pauses the rotation.An ability you press yourself
C"channel": trueLike a normal spell; while the channel goes, nothing is pressed. After the press the slot isn't pressed again until the channel starts.Killing Spree (51690)
O"offGcd": trueAt once, even mid-GCD.Keep It Rolling (381989)
O + Q"offGcd": true, "queue": trueOff the GCD, but only in the queue window at the end of the GCD.The Adrenaline Rush + Killing Spree macro (13750); Vanish (1856) after Kingsbane
C + O"channel": true, "offGcd": trueA channel that starts off the GCD: at once, then nothing until the channel ends.Rare
C + O + Qall threeThe same, in the queue window.Rare
Q without O"queue": trueError: "queue only together with offGcd".—
M + others"manual": true, …M wins: the slot isn't pressed, the other flags don't matter.—

Why O + Q and not just O: a button with O alone is pressed at once, mid-GCD, before the previous hit has landed. Vanish after Kingsbane with O alone would be pressed before Kingsbane's combo points arrive, and the Vanish rule (Combo_Points < 65) would check the old value. With Q the button waits for the end of the GCD.

Strict priority: an off-GCD rule lower in the list doesn't overtake a higher rule that is waiting for the GCD.

JSON
"slots": [
  {"n": 1, "alias": "Adrenaline_Rush", "label": "Adrenaline Rush (macro with Killing Spree)", "spell": 13750, "offGcd": true, "queue": true},
  {"n": 4, "alias": "Keep_It_Rolling", "label": "Keep It Rolling", "spell": 381989, "offGcd": true},
  {"n": 8, "alias": "Killing_Spree", "label": "Killing Spree", "spell": 51690, "channel": true},
  {"n": 11, "alias": "Crimson_Vial", "label": "Crimson Vial", "spell": 185311, "show": "combat"}
]

rules#

Rules in priority order: the first in the list is checked first.

FieldTypeRequiredDefaultWhat it is
usestringyes—The slot to press: its alias or slotN.
namestringno""The action's name, shown in the log. In the text, the comment on the use line.
notestring or list of stringsno[]Notes above the rule (# lines before use).
whenlist of groupsno[]OR groups. Empty or missing: a rule without conditions, pressed whenever the spell can be used.

when: groups and conditions#

FieldTypeRequiredWhat it is
allnon-empty list of stringsyesThe group's conditions, all must hold (AND).
commentstringnoThe group's comment (in the text, at the end of its line).
JSON
{
  "use": "Garrote",
  "name": "Garrote",
  "note": ["Refresh in the pandemic window"],
  "when": [
    {"all": ["Garrote_DoT.down"], "comment": "no DoT"},
    {"all": ["Garrote_DoT.up", "Garrote_DoT.duration <= 30"], "comment": "30% or less left"}
  ]
}

One slot may appear in several rules: for example, Garrote from Stealth high in the list and ordinary Garrote upkeep lower down.

Conditions in JSON#

Each condition is a string in KRC syntax: Garrote_DoT.down, Rupture_DoT.duration <= 25, Kingsbane.cd <= 3, Combo_Points >= 70, enemies >= 2, target.distance <= 8, cd == 1. The full list is in Conditions.

  • Refer by name (Garrote_DoT) or by number: buff1, slot3, resource4.
  • and inside a string splits it into two conditions: "Combo_Points >= 70 and Energy >= 70" is two conditions, and both count toward the limit of 16.
  • No # and no line breaks in a string.
  • KeyRota's own engine reads the conditions, so the JSON and the text always mean the same.

Names#

A name (alias) is how auras, slots and resources are called in conditions and in use.

  • The first character is a letter (any alphabet) or _; then letters, digits 0–9 and _. Cyrillic is fine; spaces, hyphens and dots are not: Fan_of_Knives, not Fan-of-Knives.
  • A name is unique among all auras, slots and resources.
  • Reserved: use, when, and, or, rotation, gcd, enemies, target, aoe, st, cd, defensives, ground_spells, movement, and names like buffN, slotN, resourceN.
  • Keywords are lowercase only: use, or, .up. Use, OR and .UP are errors.

A name from the label. Without alias, KeyRota makes one from label: brackets and what's inside them are dropped, letters and digits stay, everything else becomes _. for example, "Garrote on target [703]" becomes Garrote_on_target. An empty or unusable name becomes Aura_N or Slot_N; a taken one gets _2, _3. If a rule refers to buffN or slotN missing from the lists, they are declared automatically as Aura_N and Slot_N.

Required fields and defaults#

WhereRequiredDefaults
Fileformat, versiontitle "", specID 0, ignoreMainIcon true, enemies {"range": 8, "combatOnly": true}, empty lists
Auranalias from label, label "", spell 0
Slotnshow "target", all flags false, spell 0
Ruleusename "", note [], when [] (no conditions)
Groupall (non-empty)comment ""

Limits#

WhatLimit
Auras12 (n 1–12)
Slots12 (n 1–12)
OR groups in a rule16
AND conditions in a group16
Rulesunlimited
Number in a conditioninteger 0–100
enemies.range1–100
spell0–100000000
specID0–100000
Resourcesup to 99

Exactly 16 groups of 16 conditions is fine; 17 is an error.

Errors#

A rotation with an error doesn't load at all: there is no partial execution. An error always names the place. In JSON it's a path: rules[3].when[1]: unknown name Foo (counting from zero). In the builder, "Rule 4, group 2: unknown name Foo" (counting from one). In KRC text, a line number.

JSON structure:

MessageCause
unexpected field “offgcd”A misspelt or unknown field (case matters: offGcd).
missing field “format”A required field is missing.
format: expected “keyrota-rotation”Another value of format.
version: version 1 requiredNo version, or below 1.
slots[0]: missing number n (1–12)A slot or aura without n.
slots[2].n: number outside 1..12n out of range.
slots[3]: number 4 is already takenTwo slots (or two auras) with the same n.
slots[1].show: expected “target” or “combat”Another value of show.
slots[0]: queue only together with offGcdqueue: true without offGcd: true.
rules[2].use: no slot “Garotte”use refers to a slot that doesn't exist.
rules[0].when[1].all: a non-empty list of conditions is requiredAn empty group or no all.
rules[0].when[0].all[2]: condition is empty or contains # or a line breakAn empty condition string, or # in it.
….spell: expected an integerWrong type: a string instead of a number, true instead of a number and so on.

Conditions and declarations (the same as in KRC text):

CodeExample messageCause
unknown_refunknown name FooNo aura, slot or resource with that name.
unknown_refresource5 is not declaredThe resource isn't declared in resources.
wrong_kindGarrote is a slot, expected a buffAn aura property on a slot (Garrote.up) or the other way round.
bad_conditioncan't parse condition "Energy => 50"A typo in the condition or the operator.
bad_condition"Garrote.distance <= 8": only target has distanceOnly the target has a distance.
value_rangevalue 120 outside 0..100A number above 100. There are no negative or fractional numbers.
toggle_value"cd >= 1": a toggle only takes == 0 or == 1A toggle is compared only with == and 0 or 1.
bad_aliasinvalid name Fan-of-KnivesInvalid characters in a name.
reserved_aliasname cd is reservedA reserved name.
duplicate_aliasname Garrote is already declaredTwo auras, slots or resources with one name.
index_rangebuff13: number outside 1..12A reference to a number above 12.
too_many_groupsmore than 16 OR groupsMore than 16 groups in a rule.
too_many_conditions17 conditions in a group (maximum 16)More than 16 conditions in a group.

The builder also reports "the group has no conditions" (an empty group), "no slot selected" and "quotes aren't allowed in the name".

KRC text#

KRC text is the same rotation as lines. The Text and JSON page shows it, and you can keep it as a .txt file. Text and JSON convert into each other without losing meaning.

KRC text
rotation "Assassination: minimum"

buff1	Rupture_DoT = "Rupture on target [1943]"
slot2	Rupture = "Rupture"
slot4	Mutilate = "Mutilate"
resource1	PlayerHP
resource2	TargetHP
resource3	Energy
resource4	Combo_Points

use Rupture when  # Rupture
	Combo_Points >= 70 and Rupture_DoT.down  # 5+ combo points, no bleed

use Mutilate when  # Mutilate

Lines#

  • # outside double quotes starts a comment up to the end of the line. Inside a "label", # is an ordinary character.
  • Blank lines and comment-only lines are skipped: that's how descriptions and separators are written.
  • Any other line is a declaration, use, or or a condition line. Otherwise it's a stray_line error: "line outside a rule: "…" (start prose with #)".
  • Any spaces and tabs between parts. The canonical form uses a tab after buffN/slotN and at the start of condition lines.

Declarations#

Only before the first use:

KRC text
rotation "Name"                    # optional, at most once
buff1	Garrote_DoT = "Garrote on target [703]"   # aura 1
slot1	Garrote = "Garrote"                         # slot 1
resource1	PlayerHP                               # resource 1

buffN and slotN are 1–12, resourceN 1–99. The quoted label may be empty: slot5 Filler = "". A declaration after the first use is a late_declaration error.

Rules#

KRC text
use Rupture when  # Rupture
	Combo_Points >= 70 and Rupture_DoT.down  # no DoT
	or
	Combo_Points >= 70 and Rupture_DoT.up and Rupture_DoT.duration <= 30  # pandemic
  • use Slot when opens a rule; a comment on that line is the action's name.
  • Each condition line is one group (conditions joined with and). Groups are separated by a line with the single word or.
  • A rule without condition lines has no conditions.
  • Two condition lines in a row without or is a split_group error; or at the start, at the end or twice in a row is empty_group.

Text and JSON: how they map#

In the textIn JSON
rotation "…"title
# … lines before the first usenotes
A comment at the end of a buffN / slotN lineThe aura's / slot's note
# … lines right before use (and between the rule's groups)The rule's note
A comment on the use lineThe rule's name
A comment at the end of a condition lineThe group's comment
# … lines after the last rulefooter
— (not in the text)specID, ignoreMainIcon, enemies, spell, show, flags

Blank lines at the edges of comment blocks are dropped; inside they stay as "".

Import and storage#

  • KeyRota keeps the rotation inside the profile. Export in the main window saves the KeyRota profile (JSON) with its rotation; Import accepts a KeyRota profile and a bare rotation JSON.
  • New — From file… accepts .txt with KRC text, rotation JSON and a KeyRota profile; New — From clipboard, the same from the clipboard.
  • In the builder: Text and JSON — Paste or Open… replace the rotation; slots and auras with the same numbers keep their IDs, With a target / In combat and flags if the new version has none.
  • A text rotation (without JSON) takes the slots' IDs and visibility from the Better Assistant profile already stored in the KeyRota profile, and the auras' IDs also from [703] marks in their labels.

The Better Assistant profile from JSON#

"Build from slots and auras" in the builder makes the Better Assistant profile string from the rotation JSON:

  • 12 slots: slot N is slots[n = N].spell, shown by show. A slot without an ID stays empty.
  • Auras in order from the first: auras[n = 1].spell, auras[n = 2].spell… An aura without an ID in the middle is an error.
  • Class and specialization from specID.
  • Enemy counter from enemies.range and enemies.combatOnly.
  • If the KeyRota profile already holds a Better Assistant profile of the same specialization, its other settings and the matching slots and auras are kept.

In the game: /ba — Profile — paste — Apply.

JSON Schema#

The format's schema: keyrota-rotation.schema.json (draft 2020-12). It checks fields, types, ranges, queue without offGcd and the shape of condition strings. It doesn't check that n and names are unique, that names in conditions exist, or the limit of 16 after splitting strings at and : KeyRota does that on load.

To check a file:

Text
npx ajv-cli validate --spec=draft2020 -s keyrota-rotation.schema.json -d my-rotation.json

Don't put a "$schema" field into a rotation file: KeyRota takes it for an unexpected field.