Decks

List Decks

GET/api/v1/decks
Scopedecks:read
AuthBearer token
PaginatedCursor
Rate limit60 / minute

Returns a paginated list of the user's decks.

ParameterRequiredDescription
limitNoNumber of results (1-100, default 20)
cursorNoCursor from previous response for pagination
Response
{
  "data": [
    {
      "id": "660f9500-...",
      "name": "Atraxa Superfriends",
      "format": "commander",
      "commander_name": "Atraxa, Praetors' Voice",
      "engine_version": "4.6",
      "target_bracket": 3,
      "bracket": {
        "target": 3,
        "actual": 2,
        "status": "ok",
        "checked_at": "2026-03-20T12:04:11Z",
        "basis": "compliance",
        "measures": "Game changer count and illegal or banned combo count. Not a power rating.",
        "max_detectable": 4
      },
      "origin": "built",
      "import_source": null,
      "build": {
        "tier": "Mythic",
        "pool_size": 1843,
        "pool_size_meaning": "Distinct cards in the source collection that are legal in the commander's colour identity, not the size of the collection.",
        "completed_at": "2026-03-20T12:03:58Z"
      },
      "created_at": "2026-03-20T12:00:00Z"
    }
  ],
  "meta": {
    "has_more": true,
    "next_cursor": "660f9500-..."
  }
}

Caching Spellweave Analysis

Responses that carry computed analysis, such as /decks/{id}/stats?include= and the Deck Lab, tell you how long you may keep the result. The period appears twice: as a Cache-Control header for your HTTP client, and as meta.cache for you.

Cache-Control: private, max-age=86400

"meta": {
  "cache": {
    "max_age_seconds": 86400,
    "expires_at": "2026-09-07T18:28:39Z"
  }
}

The Developer Terms bind you to this period rather than to a number written into the terms, so it can be tuned per endpoint without a new version of the agreement. privateis deliberate: this is one user's analysis behind their token and must not be stored in a shared or CDN cache. Store expires_at alongside anything you persist, and re-fetch after it passes. Raw user data such as deck and collection lists carries no such limit.

Reading the bracket

bracket.target is what the user asked for (1 to 5, or null for no preference). bracket.actual is what our deterministic checker found, and it is a compliance readout, not a measure of how strong the deck is. Its only inputs are the game changer count and the illegal or banned combo count, so a well-tuned deck with neither will read Bracket 2. Do not present it to your users as a power rating.

It ranges 1 to 4 and never returns 5, because cEDH is a metagame designation that a card list alone cannot certify. A deck targeting 5 whose actual is 4 is at the top of what the checker can see, and is not behind its target.

The check runs after the build, so actual is null until it completes. Always read status before actual: pending means not computed yet, ok means the value is good, failed means the check errored, and unsupported means the format has no checker. A null actual never means bracket zero.

Built or imported

origin is built for decks our engine built and imported for decks brought in from elsewhere, where import_source names the platform. build is present only for built decks. Inside it, tier is the optimization tier the build ran at (Uncommon, Rare or Mythic), and pool_size is how many distinct cards the optimizer had to choose from: the cards in the source collection that are legal in the commander's colour identity. It is not the size of the collection, and a 20,000-card collection can present a pool of under 2,000 for a mono-colour commander. Either field can be null for older decks whose build record predates them; null means unknown, never zero.

Get Deck

GET/api/v1/decks/{id}
Scopedecks:read
AuthBearer token
PaginatedNo
Rate limit60 / minute

Returns a deck with all its cards.

Response
{
  "data": {
    "id": "660f9500-...",
    "name": "Atraxa Superfriends",
    "format": "commander",
    "commander_name": "Atraxa, Praetors' Voice",
    "commander_oracle_id": "a1b2c3d4-...",
    "engine_version": "4.6",
    "target_bracket": 3,
    "bracket": {
      "target": 3,
      "actual": 2,
      "status": "ok",
      "checked_at": "2026-03-20T12:04:11Z",
      "basis": "compliance",
      "measures": "Game changer count and illegal or banned combo count. Not a power rating.",
      "max_detectable": 4
    },
    "origin": "built",
    "import_source": null,
    "build": {
      "tier": "Mythic",
      "pool_size": 1843,
      "pool_size_meaning": "Distinct cards in the source collection that are legal in the commander's colour identity, not the size of the collection.",
      "completed_at": "2026-03-20T12:03:58Z"
    },
    "created_at": "2026-03-20T12:00:00Z",
    "cards": [
      {
        "oracle_id": "a1b2c3d4-...",
        "name": "Atraxa, Praetors' Voice",
        "mana_cost": "{G}{W}{U}{B}",
        "mana_value": 4.0,
        "type_line": "Legendary Creature — Phyrexian Angel Horror",
        "quantity": 1,
        "is_commander": true,
        "category": "commander"
      },
      {
        "oracle_id": "e5f6g7h8-...",
        "name": "Deepglow Skate",
        "mana_cost": "{4}{U}",
        "mana_value": 5.0,
        "type_line": "Creature — Fish",
        "quantity": 1,
        "is_commander": false,
        "category": "superfriends"
      }
    ]
  }
}

Deck Statistics

GET/api/v1/decks/{id}/stats
Scopedecks:read
AuthBearer token
PaginatedNo
Rate limit60 / minute

Card counts by type, land count and average mana value. Pass include to add the deeper sections Spellweave computes for the deck page. Everything here is deterministic and costs the same whether you ask for one section or all of them.

The response WITHOUT include is unchanged, so existing integrations are unaffected. Adding include adds keys inside data and an attribution block; it never removes or renames anything.

ParameterRequiredDescription
includeNoComma-separated: fundamentals, curve, bracket, or all. An unrecognised value returns 400 invalid_include rather than being ignored.
includeAdds
fundamentalsarchetype, secondary_archetype, and fundamentals: per-role actual, target and gap.
curvemana_curve: the histogram. Land count and average mana value are already in the base response and are not repeated.
bracketbracket: the Commander bracket with its name, game changers, and detected combos.
allAll of the above.

This is NOT the AI Analysis. That is a separate paid feature and is not available through the API yet.

Request
curl "https://public-api.spellweave.app/api/v1/decks/{id}/stats?include=fundamentals,bracket"   -H "Authorization: Bearer $SPELLWEAVE_TOKEN"
Response
{
  "data": {
    "deck_id": "660f9500-...",
    "total_cards": 100,
    "total_quantity": 100,
    "creature_count": 28,
    "spell_count": 18,
    "artifact_count": 12,
    "enchantment_count": 8,
    "land_count": 36,
    "avg_mana_value": 3.14,

    "archetype": "counters_plus1",
    "secondary_archetype": "enchantress",
    "fundamentals": {
      "ramp":    { "actual": 12, "target": 10, "gap": 0 },
      "draw":    { "actual": 8,  "target": 10, "gap": 2 },
      "removal": { "actual": 9,  "target": 10, "gap": 1 }
    },

    "bracket": {
      "bracket": 2,
      "bracket_name": "Core",
      "game_changers": [],
      "combos": []
    }
  },
  "attribution": {
    "text": "Deck analysis by Spellweave",
    "url": "https://spellweave.app",
    "required": true,
    "notice": "If you display this data in your product, you must disclose that it comes from spellweave.app. See the Spellweave Developer Terms of Service."
  },
  "meta": { "request_id": "req_xyz", "timestamp": "2026-09-05T19:00:00Z" }
}

Import a Deck

POST/api/v1/decks
Scopedecks:write
AuthBearer token
Request bodyJSON
Formatscommander
Rate limit60 / minute

Imports a deck into the authenticated user's account. Send either a decklist or a Moxfield or Archidekt deck URL in deck. Both go through the same parser and the same validation the Spellweave app uses, so a deck that imports here imports there.

ParameterRequiredDescription
deckYesA decklist, or a Moxfield/Archidekt deck URL. Max 100,000 characters.
formatNoDefaults to commander. Only commander is accepted today; standard is recognised and returns format_not_yet_supported.
nameNoDeck name. Derived from the source when omitted.
sourceNoYour own provenance label, stored on the deck. Defaults to api.
Request
{
  "deck": "https://moxfield.com/decks/xxxxxxxxxxxxxxx",
  "format": "commander",
  "name": "Atraxa Superfriends"
}
Response (201)
{
  "data": {
    "id": "660f9500-...",
    "name": "Atraxa Superfriends",
    "format": "commander",
    "commander": { "name": "Atraxa, Praetors' Voice", "oracle_id": "..." },
    "partner": null,
    "requested_cards": 100,
    "resolved_cards": 100,
    "unresolved_cards": [],
    "warnings": [],
    "detected_source": "moxfield"
  },
  "attribution": {
    "text": "Deck analysis by Spellweave",
    "url": "https://spellweave.app",
    "required": true,
    "notice": "If you display this data in your product, you must disclose that it comes from spellweave.app. See the Spellweave Developer Terms of Service."
  }
}

resolved_cards can be lower than requested_cards. Cards that did not resolve are listed in unresolved_cards and are NOT in the deck, so show them to your user rather than assuming the import was complete.

Deck requirements

Commander decks must be exactly 100 cards including the commander. A deck with two partner commanders is 98 + 2, which is also exactly 100. The commander must be legal as a commander on its FRONT face: a card whose Legendary Creature type sits on the back is rejected.

Errors

Every failure carries a machine-readable code. Size failures also carry a details object with the numbers needed to fix the deck, so you can tell your user exactly what to change rather than that something went wrong.

Error (400)
{
  "error": {
    "code": "INVALID_DECK_SIZE",
    "message": "This Commander deck has 98 cards. Commander decks must have exactly 100 cards including the commander. Add 2 cards and try again.",
    "details": {
      "totalCards": 98,
      "expected": 100,
      "delta": 2,
      "direction": "add",
      "commanderZoneCount": 1,
      "format": "commander"
    },
    "documentation": "https://dev.spellweave.app/docs/errors/"
  }
}
CodeMeaning
INVALID_DECK_SIZENot exactly 100 cards. details tells you how many to add or remove.
INVALID_COMMANDERThe detected commander is not legal as a commander (front face is checked).
NO_COMMANDERNo commander could be detected. Mark it with a Commander section or category.
NO_CARDS_FOUNDNothing parseable in deck.
COMMANDER_NOT_FOUNDThe commander name did not match a card.
UNSUPPORTED_URLOnly Moxfield and Archidekt deck URLs are supported.
URL_FETCH_FAILEDThe deck URL could not be fetched. It may be private.
URL_FETCH_EMPTYThe URL returned no deck data.
DECK_TEXT_TOO_LONGOver 100,000 characters.
deck_requireddeck was missing or blank.
invalid_formatUnrecognised format.
format_not_yet_supportedA known format that this endpoint does not accept yet.
DECK_SLOT_LIMITThe user is at their deck slot limit. Surface this to them; retrying will not help.

Deck Lab

GET/api/v1/decks/{id}/lab
Scopedecks:read
AuthBearer token
EntitlementPaid
Rate limit60 / minute

The Deck Lab goldfish simulation: speed timeline, representative game, and distributions over a thousand simulated games.

This is a paid feature and is not available for every deck. It unlocks when the deck owner runs an AI Analysis, or completes a Deck Tune, on spellweave.app. Until then this endpoint returns 403 lab_not_unlocked with an unlock_url you can send the user to. A deck that is unlocked but whose Lab has never been generated returns 404 lab_not_generated.

Analysis and Tune

POST/api/v1/decks/{id}/analysis
Scopedecks:write
AuthBearer token
StatusNot implemented
POST/api/v1/decks/{id}/tune
Scopedecks:write
AuthBearer token
StatusNot implemented

Both routes exist and return 501 not_implementedtoday, so you can tell "not built yet" from a bad request while you build against the API. AI Analysis and Deck Tune are paid features on spellweave.app; exposing them here is planned but not scheduled.

Attribution

Every response carrying computed deck data includes an attribution block. If you display any of this data in your product, you must disclose to your users that it comes from spellweave.app. This is a condition of the Spellweave Developer Terms of Service, which you accepted when you registered your app.

The block travels with the data so the requirement is visible where you consume it, not only in a document. A link reading "Deck analysis by Spellweave" pointing at spellweave.app satisfies it.