Sign UpLog In

Convoke Game Submissions

Docs

  • Connect with Play Nice
  • Convoke game submissions
    • Overview
    • Submit a game
    • Game fields
    • Seat fields
    • Opening hand
    • Allowed values
    • Response
    • How games are recorded
    • Linked accounts
    • Errors
    • Existing users
  • Opening hands

Overview

Send each finished Convoke match to Play Nice. It's recorded as a published game on the submitter's account.

Authenticate with your partner key in an Authorization: Bearer header. It's the same key you use as your Connect with Play Nice client_secret.

Endpoint
POST https://api.playnicemtg.com/v1/convoke
Authorization: Bearer YOUR_PARTNER_KEY
Content-Type: application/json

Submit a game

POST/v1/convoke

The body is JSON with two fields.

FieldTypeDescription
userIdstringRequired. The submitter's Play Nice user id, from the token exchange.
gameDataobjectRequired. The match. See Game fields.

Resubmitting is safe. The same convoke_match_id returns the game already recorded.

Request
curl -X POST https://api.playnicemtg.com/v1/convoke \
  -H 'Authorization: Bearer YOUR_PARTNER_KEY' \
  -H 'Content-Type: application/json' \
  -d @game.json
game.json
{
  "userId": "Xy12AbCdEfGh34IjKlMn56OpQr78",
  "gameData": {
    "convoke_user_id": "convoke-user-123",
    "convoke_match_id": "match-8841",
    "format": "cedh",
    "no_of_players": 4,
    "isDraw": false,
    "winner_player_id": "convoke-user-123",
    "ending_turn": "6",
    "win_type": "win-first",
    "win_condition": "Thassa's Oracle",
    "best_card1": "Rhystic Study",
    "best_card2": "Ad Nauseam",
    "fun_factor": "8",
    "notes": "Won through a Silence.",
    "tournament": false,
    "topdeck_tournament": false,
    "league": false,
    "needed_one_more_turn": false,
    "opening_hand": {
      "card1": "Sol Ring",
      "card2": "Rhystic Study",
      "card3": "Island",
      "card4": "Mana Vault",
      "card5": "Swamp",
      "card6": "Dark Ritual",
      "card7": "Demonic Tutor"
    },
    "seats": [
      {
        "position": "1",
        "commander": "63cda4a0-0dff-4edb-ae67-a2b7e2971350",
        "partner_commander": null,
        "convoke_user_id": "convoke-user-123",
        "play_nice_user_id": "Xy12AbCdEfGh34IjKlMn56OpQr78",
        "mulligan": "7 (1st)",
        "lands_in_opening_hand": 2
      },
      {
        "position": "2",
        "commander": "557fcd17-6cb3-414a-b2b1-ea9ae32e5aec",
        "partner_commander": "bc7cbe9b-324e-42b8-94e2-36e91cb32163",
        "convoke_user_id": "convoke-user-456",
        "play_nice_user_id": null,
        "mulligan": "6",
        "lands_in_opening_hand": 3
      },
      {
        "position": "3",
        "commander": "63cda4a0-0dff-4edb-ae67-a2b7e2971350",
        "partner_commander": null,
        "convoke_user_id": "convoke-user-789",
        "play_nice_user_id": null,
        "mulligan": null,
        "lands_in_opening_hand": null
      },
      {
        "position": "4",
        "commander": "557fcd17-6cb3-414a-b2b1-ea9ae32e5aec",
        "partner_commander": null,
        "convoke_user_id": "convoke-user-012",
        "play_nice_user_id": null,
        "mulligan": null,
        "lands_in_opening_hand": null
      }
    ]
  }
}

Game fields

Optional fields with an invalid value are dropped or defaulted, not rejected.

FieldTypeDescription
convoke_user_idstringRequired. The submitter's Convoke id. Must match a seat.
convoke_match_idstringRequired. Your unique match id.
no_of_playersintegerRequired. 2–5.
isDrawbooleanRequired.
winner_player_idstringRequired unless isDraw. The winner's convoke_user_id.
seatsarrayRequired. One per player. See Seat fields.
formatstringSee Allowed values. Default casual.
ending_turnstring"2"–"99". "1" means not tracked.
win_typestringWin reason. Ignored for draws.
draw_typestringDraw reason. Draws only.
win_conditionstringThe combo or action that won. Up to 200 characters.
best_card1stringMVP card name. Up to 100 characters.
best_card2stringSecond MVP card name. Up to 100 characters.
fun_factorstring"1"–"10".
notesstringUp to 10,000 characters.
tournamentbooleanDefault false.
topdeck_tournamentbooleanDefault false.
leaguebooleanDefault false.
needed_one_more_turnbooleanThe submitter would have won next turn. Default false.
opening_handobjectThe submitter's opening hand. See Opening hand.

Seat fields

seats must hold exactly no_of_players entries.

FieldTypeDescription
positionstringRequired. "1" to no_of_players, unique.
commanderstringScryfall card id, any printing. Required on the submitter's seat; an opponent seat without one is recorded as an unknown deck.
convoke_user_idstringRequired. The pilot's Convoke id, unique per game.
partner_commanderstring | nullScryfall card id of the partner or background.
play_nice_user_idstring | nullThe pilot's Play Nice user id. See Linked accounts.
mulliganstring | nullSee Allowed values.
lands_in_opening_handinteger | null0–7.

Opening hand

The submitter's opening hand, as card names in card1–card7. Leave out slots the hand didn't have.

Send names, not ids: Play Nice matches each name to the printing in the user's decklist. Names it can't match are dropped.

After a mulligan to 5
"opening_hand": {
  "card1": "Sol Ring",
  "card2": "Arcane Signet",
  "card3": "Command Tower",
  "card4": "Rhystic Study",
  "card5": "Island"
}

Allowed values

format

ValueMeaning
casualCasual
high-powerHigh Power
cedhcEDH

mulligan: the hand size kept, exactly as written. 7 (1st) and 7 (2nd) are a seven kept on the first or second hand.

Value
7 (1st)
7 (2nd)
6
5
4
3
2
1

win_type

ValueMeaning
win-firstWin First
win-throughWin Through
win-afterWin After
win-on-topWin on Top
win-becauseWin Because

draw_type

ValueMeaning
timeWent to Time
forcedForced Draw
idIntentional Draw
infiniteAccidental Infinite Loop
nobodyNobody Wanted to Play Anymore
staxStax Player Couldn't Win

Response

FieldTypeDescription
game_idstringThe Play Nice game id.
game_urlstringThe game on the submitter's profile.
share_game_urlstringOther players open it to add a copy of the game to their own account.
convoke_match_idstringEchoed back.
Response · 200
{
  "success": true,
  "game_id": "3f1c9a2e-5b7d-4e8f-9a0b-1c2d3e4f5a6b",
  "game_url": "https://www.playnicemtg.com/users/kinnanfan/games/3f1c9a2e-5b7d-4e8f-9a0b-1c2d3e4f5a6b",
  "share_game_url": "https://www.playnicemtg.com/s/aB3dE9",
  "convoke_match_id": "match-8841"
}

How games are recorded

  • Dated when Play Nice receives it, with venue Convoke and marked online.
  • Added to the submitter's deck for that commander. The deck is created if it doesn't exist.
  • A missing or invalid commander fails the request for the submitter (400 or 422), and becomes an unknown deck for an opponent.
  • A partner pair with one invalid side is recorded as the valid commander.
  • The submitter gets a Play Nice notification, unless they've turned notifications off.

Linked accounts

Once enforcement is on, submissions require linked accounts:

  • userId must be linked to the submitter's convoke_user_id, or the request fails with 403 NOT_LINKED.
  • A seat's play_nice_user_id counts only if that Play Nice account is linked to that seat's convoke_user_id. Otherwise the seat is recorded without a Play Nice account.
  • Ids come from your server, never from a client payload.

On NOT_LINKED,don't retry. Clear the stored Play Nice id and ask the user to link. Enforcement starts on a date we agree with you.

Response · 403
{
  "success": false,
  "error": {
    "code": "NOT_LINKED",
    "message": "This Convoke account is not linked to that Play Nice account. Clear the stored Play Nice ID and ask the user to link their account."
  }
}

Errors

Errors return error.code and error.message, plus error.field when one field is at fault.

StatusCodeWhat to do
400BAD_JSONSend valid JSON.
400VALIDATION_FAILEDFix the field named in error.field.
401UNAUTHORIZEDCheck the partner key.
403NOT_LINKEDAsk the user to link. Don't retry.
404USER_NOT_FOUNDuserId isn't a Play Nice user.
409CONFLICTThis convoke_match_id was already recorded for another user.
422INVALID_COMMANDERThe submitter's commander isn't a legal commander or companion.
500INTERNAL_ERRORTemporary. Retry with the same body.
Response · 400
{
  "success": false,
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "gameData.winner_player_id is required when isDraw is false.",
    "field": "gameData.winner_player_id"
  }
}

Existing users

Users who pasted their Play Nice id into Convoke don't need to link again:

  1. Ship the link flow.
  2. Remove the manual Play Nice id field.
  3. Send us a one-time export of every convoke_user_id / play_nice_user_id pair.

We create links from it. A Play Nice id claimed by more than one Convoke account is skipped; those users link through the flow.

Export format
[
  { "convoke_user_id": "convoke-user-123", "play_nice_user_id": "Xy12AbCdEfGh34IjKlMn56OpQr78" },
  { "convoke_user_id": "convoke-user-456", "play_nice_user_id": "Pq98RsTuVwXy76ZaBcDe54FgHi32" }
]
  • Play Nice
  • About Us
  • Pricing
  • BubbleCut
  • Middle Age Magic
  • Support
  • FAQ
  • Contact Us
  • Legal
  • Privacy Policy
  • Terms of Service
Wizards of the Coast, Magic: The Gathering, and their logos are trademarks of Wizards of the Coast LLC in the United States and other countries. © 1993-2026 Wizards of the Coast LLC. All Rights Reserved.

Play Nice and Middle Age Magic LLC is not affiliated with, endorsed, sponsored, or specifically approved by Wizards of the Coast LLC. Play Nice is using the trademarks and other intellectual property of Wizards of the Coast LLC as permitted under the Wizard's Fan Content Policy. © 2024-2026 Middle Age Magic LLC. All Rights Reserved.

"Mountain of Skulls" artwork by Kathleen Neeley for Play Nice.