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

# Restore Campaign State

> Install a saved campaign state document wholesale — the undo for a bad write.

Written after a board migration replaced a live league's ``cells`` outright:
the hand-authored ``map_spec`` s, their rendered previews and the per-cell
``game_config`` went with them, and every historical frame's ``owners`` was
emptied. Nothing could put a saved copy back a field at a time — ``cell-map``
404s for a cell id the damaged board no longer has, so per-cell restoration
cannot even BEGIN until a board of the right shape exists again.

A raw wholesale write does already exist: ``PUT
/v2/leagues/{league_id}/commissioner-state`` validates a campaign document's
SHAPE and stores it. Shape is the least of what can be wrong with a restore,
and this route is that write carrying the guards the incident showed were
missing — see ``_assert_restorable`` for the refusals, all of which name the
disagreement and which side has to move.

THE EPOCH IS NOT RESTORED, IT IS FORCED FORWARD to ``max(stored, document) + 1``.
Taking it from the document is the obvious reading and the wrong one, twice.
A round that started against the board being replaced persists through
``runner._write_state``, which drops that write only when the epoch has
MOVED (``fresh.epoch != state.epoch``) — so a document whose epoch happens to
equal the stored one leaves an in-flight round free to write its whole
state, computed against the damaged board, over the restore seconds later.
And the epoch namespaces round idempotency keys
(``campaign:{league}:e{epoch}:{n}``), so restoring an earlier epoch would let
the coming rounds collide with keys the previous war already spent and adopt
its orphans. Forcing it forward costs one field of byte-identity and buys
both; the document round-trips exactly otherwise. The residual race is
``_write_state``'s own unlocked re-read, which predates this route.

Dry-run by default, like migrate-board. The dry report is the whole point of
running it first: it counts what the restore gives back and what it takes
away against what is stored right now.



## OpenAPI

````yaml https://softmax.com/api/observatory/openapi.json post /v2/leagues/{league_id}/campaign/restore-state
openapi: 3.1.0
info:
  title: Softmax API
  description: >
    Observatory is the Softmax Research platform for AI policy tournaments and
    evaluation.

    Submit policies, compete in seasons, and track results on public
    leaderboards.


    ---


    ## Authentication


    Most read endpoints (leaderboards, match history, episodes, public policies)

    work without auth. Submitting policies or accessing your own data requires

    a Bearer token:


    ```

    Authorization: Bearer <your-token>

    ```


    To get a token, install [cogames](https://pypi.org/project/cogames/) and
    run:


    ```bash

    cogames auth login

    ```


    A `401` response means no token was provided. A `403` means the token

    is invalid or expired.


    ### Verify your token


    ```bash

    curl -H "Authorization: Bearer <your-token>" \
      https://softmax.com/api/observatory/whoami
    ```


    Returns `{"user_email": "you@example.com"}` on success.
  version: 1.0.0
servers:
  - url: https://softmax.com/api/observatory
security: []
paths:
  /v2/leagues/{league_id}/campaign/restore-state:
    post:
      tags:
        - v2
        - v2
      summary: Restore Campaign State
      description: >-
        Install a saved campaign state document wholesale — the undo for a bad
        write.


        Written after a board migration replaced a live league's ``cells``
        outright:

        the hand-authored ``map_spec`` s, their rendered previews and the
        per-cell

        ``game_config`` went with them, and every historical frame's ``owners``
        was

        emptied. Nothing could put a saved copy back a field at a time —
        ``cell-map``

        404s for a cell id the damaged board no longer has, so per-cell
        restoration

        cannot even BEGIN until a board of the right shape exists again.


        A raw wholesale write does already exist: ``PUT

        /v2/leagues/{league_id}/commissioner-state`` validates a campaign
        document's

        SHAPE and stores it. Shape is the least of what can be wrong with a
        restore,

        and this route is that write carrying the guards the incident showed
        were

        missing — see ``_assert_restorable`` for the refusals, all of which name
        the

        disagreement and which side has to move.


        THE EPOCH IS NOT RESTORED, IT IS FORCED FORWARD to ``max(stored,
        document) + 1``.

        Taking it from the document is the obvious reading and the wrong one,
        twice.

        A round that started against the board being replaced persists through

        ``runner._write_state``, which drops that write only when the epoch has

        MOVED (``fresh.epoch != state.epoch``) — so a document whose epoch
        happens to

        equal the stored one leaves an in-flight round free to write its whole

        state, computed against the damaged board, over the restore seconds
        later.

        And the epoch namespaces round idempotency keys

        (``campaign:{league}:e{epoch}:{n}``), so restoring an earlier epoch
        would let

        the coming rounds collide with keys the previous war already spent and
        adopt

        its orphans. Forcing it forward costs one field of byte-identity and
        buys

        both; the document round-trips exactly otherwise. The residual race is

        ``_write_state``'s own unlocked re-read, which predates this route.


        Dry-run by default, like migrate-board. The dry report is the whole
        point of

        running it first: it counts what the restore gives back and what it
        takes

        away against what is stored right now.
      operationId: >-
        restore_campaign_state_v2_leagues__league_id__campaign_restore_state_post
      parameters:
        - name: league_id
          in: path
          required: true
          schema:
            type: string
            pattern: >-
              ^league_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$
            title: League Id
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RestoreStateRequest'
      responses:
        '200':
          description: Successful Response
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
                title: >-
                  Response Restore Campaign State V2 Leagues  League Id 
                  Campaign Restore State Post
        '422':
          description: Validation Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
      security:
        - BearerAuth: []
components:
  schemas:
    RestoreStateRequest:
      properties:
        state:
          $ref: '#/components/schemas/CampaignState'
        dry_run:
          type: boolean
          title: Dry Run
          default: true
      additionalProperties: false
      type: object
      required:
        - state
      title: RestoreStateRequest
    HTTPValidationError:
      properties:
        detail:
          items:
            $ref: '#/components/schemas/ValidationError'
          type: array
          title: Detail
      type: object
      title: HTTPValidationError
    CampaignState:
      properties:
        version:
          type: string
          const: campaign_v1
          title: Version
          default: campaign_v1
        epoch:
          type: integer
          title: Epoch
          default: 0
        round:
          type: integer
          title: Round
          default: 0
        board_width:
          anyOf:
            - type: integer
            - type: 'null'
          title: Board Width
        board_height:
          anyOf:
            - type: integer
            - type: 'null'
          title: Board Height
        board_shape:
          anyOf:
            - type: string
            - type: 'null'
          title: Board Shape
        cells:
          additionalProperties:
            additionalProperties: true
            type: object
          type: object
          title: Cells
        prompts:
          additionalProperties:
            type: string
          type: object
          title: Prompts
        prompt_history:
          additionalProperties:
            items:
              $ref: '#/components/schemas/PromptVersion'
            type: array
          type: object
          title: Prompt History
        color_prefs:
          additionalProperties:
            type: integer
          type: object
          title: Color Prefs
        perks:
          additionalProperties:
            items:
              type: string
            type: array
          type: object
          title: Perks
        seeded:
          items:
            type: string
          type: array
          title: Seeded
        frames:
          items:
            additionalProperties: true
            type: object
          type: array
          title: Frames
        events:
          items:
            additionalProperties: true
            type: object
          type: array
          title: Events
        last_round_at:
          anyOf:
            - type: string
            - type: 'null'
          title: Last Round At
        pending_round:
          anyOf:
            - additionalProperties: true
              type: object
            - type: 'null'
          title: Pending Round
      additionalProperties: false
      type: object
      title: CampaignState
    ValidationError:
      properties:
        loc:
          items:
            anyOf:
              - type: string
              - type: integer
          type: array
          title: Location
        msg:
          type: string
          title: Message
        type:
          type: string
          title: Error Type
        input:
          title: Input
        ctx:
          type: object
          title: Context
      type: object
      required:
        - loc
        - msg
        - type
      title: ValidationError
    PromptVersion:
      properties:
        prompt:
          type: string
          title: Prompt
        set_at:
          type: string
          title: Set At
        epoch:
          type: integer
          title: Epoch
        round:
          type: integer
          title: Round
      type: object
      required:
        - prompt
        - set_at
        - epoch
        - round
      title: PromptVersion
      description: |-
        One accepted standing-orders version. Deliberately NOT extra="forbid":
        an extra key written by a future version of the save endpoint must never
        make older code's `load_state` brick every board read after a rollback.
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: Bearer token issued by Softmax

````