At Sorare, we are committed to providing an open platform for developers to build upon.
While our Cards are stored on the Ethereum blockchain (or within a Starkware rollup) we support an API that provides more detailed information.
The Sorare APIs are provided by GraphQL. Sorare provides distinct APIs for Sorare: Football, Sorare: MLB and Sorare: NBA. We will move towards a federated GraphQL API in the near future.
The Sorare: Football API is hosted on https://api.sorare.com/graphql. The documentation can be found under the Docs section of the Football GraphQL playground.
The Sorare: MLB and Sorare: NBA APIs are hosted on https://api.sorare.com/sports/graphql. The documentation can be found under the Docs section of the Sports GraphQL playground.
You can easily download the GraphQL schema using [@apollo/rover](https://www.apollographql.com/docs/rover/):
$ npx -p @apollo/rover rover graph introspect https://api.sorare.com/graphql > schema-football.graphql
$ npx -p @apollo/rover rover graph introspect https://api.sorare.com/sports/graphql > schema-us-sports.graphqlThe original https://api.sorare.com/graphql API is providing all the sport-agnostic GraphQL types, fields and subscriptions which allows you to manipulate Football, MLB and NBA NFTs.
You have access to the Football-specific Card, Player, So5Fixture, So5Leaderboard, ... resources through the https://api.sorare.com/graphql API.
You have access to the MLB-specific BaseballCard, BaseballPlayer, BaseballFixture, BaseballLeaderboard, ... resources as well as the NBA-specific NBACard, NBAPlayer, NBAFixture, NBALeaderboard, ... resources through the https://api.sorare.com/sports/graphql API.
This API doesn't support authenticated API calls for now.
Each sport has their own Card types. The blockchain cards are also available as sport-agnostic Token types. Each Token belongs to a collectionName that is either football or baseball. For example, the TokenRoot type allows to query offers, auctions and nfts to get Sorare: Football, Sorare: MLB and Sorare: NBA tokens.
It also exposes 2 sport-agnostic subscriptions to get notified about any auctions & offers getting updated:
tokenAuctionWasUpdatedtokenOfferWasUpdated
While the identifier of a Sorare: Football card used to be its slug, the identifier of a sport-agnostic NFT (Token) is its assetId.
To authenticate yourself programmatically through our GraphQL API you'll need:
- your email
- the hashed version of your password
Your password needs to be hashed client-side using a salt. The salt can be retrieved with a HTTP GET request against our https://api.sorare.com/api/v1/users/<youremail> endpoint:
Example:
$ curl https://api.sorare.com/api/v1/users/myemail@mydomain.com
{"salt":"$2a$11$SSOPxn8VSUP90llNuVn.nO"}The hashed password must be computed with bcrypt:
Example in JavaScript:
import bcrypt from "bcryptjs";
const hashedPassword = bcrypt.hashSync(password, salt);Example in Ruby:
require "bcrypt"
hashed_password = BCrypt::Engine.hash_secret(password, salt)Example in Python:
import bcrypt
hashed_password = bcrypt.hashpw(password, salt)Please also make sure to set the content-type HTTP header to application/json.
For short and long-lived authentication, you should request a JWT token.
We provide JWT tokens within the signIn mutation. They can be retrieve using the following mutation:
mutation SignInMutation($input: signInInput!) {
signIn(input: $input) {
currentUser {
slug
jwtToken(aud: "<YourAud>") {
token
expiredAt
}
}
errors {
message
}
}
}It expects the following variables:
{
"input": {
"email": "your-email",
"password": "your-hashed-password"
}
}<YourAud> is a mandatory string parameter that identifies the recipients that the JWT is intended for. Your can read more about "aud" (Audience) here. We recommend to use an aud reflecting the name of your app - like myappname - to make it easier to debug & track.
$ curl 'https://api.sorare.com/graphql' \
-H 'content-type: application/json' \
-d '{
"operationName": "SignInMutation",
"variables": { "input": { "email": "<YourEmail>", "password": "<YourHashPassword>" } },
"query": "mutation SignInMutation($input: signInInput!) { signIn(input: $input) { currentUser { slug jwtToken(aud: \"<YourAud>\") { token expiredAt } } errors { message } } }"
}'
{"data":{"signIn":{"currentUser":{"slug":"<YourSlug>","jwtToken":{"token":"<YourJWTToken>","expiredAt":"..."}},"errors":[]}}}You shall then pass the token with an Authorization header alongside a JWT-AUD header to all next API requests:
$ curl 'https://api.sorare.com/graphql' \
-H 'content-type: application/json' \
-H 'Authorization: Bearer <YourJWTToken>' \
-H 'JWT-AUD: <YourAud>' \
-d '{
"operationName": "CurrentUserQuery",
"query": "query CurrentUserQuery { currentUser { slug email } }"
}'
{"data":{"currentUser":{"slug":"<YourSlug>","email":"<YourEmail>"}}}The token will expire after one year.
Please refer to the errors field to understand why a signIn mutation failed.
If currentUser is null and you don't have any errors, it's because the user has 2FA setup. Please follow the next section to handle 2FA signins.
Please note also that if the token has been issued from a specific IP address and you try to generate it from another one, 2FA will automatically activate and you will need the code sent to your email to complete authentication.
For account with 2FA enabled the signIn mutation will set the otpSessionChallenge field instead of the currentUser one.
mutation SignInMutation($input: signInInput!) {
signIn(input: $input) {
currentUser {
slug
jwtToken(aud: "<YourAud>") {
token
expiredAt
}
}
otpSessionChallenge
errors {
message
}
}
}Example:
$ curl 'https://api.sorare.com/graphql' \
-H 'content-type: application/json' \
-d '{
"operationName": "SignInMutation",
"variables": { "input": { "email": "<YourEmail>", "password": "<YourHashPassword>" } },
"query": "mutation SignInMutation($input: signInInput!) { signIn(input: $input) { currentUser { slug jwtToken(aud: \"<YourAud>\") { token expiredAt } } otpSessionChallenge errors { message } } }"
}'
{"data":{"signIn":{"currentUser":null,"otpSessionChallenge":"3a390a0661cd6f4944205f68c13fd04f","errors":[]}}}In this case, you will need to make another call to the signIn mutation and provide the otpSessionChallenge value you received and a one-time token from your 2FA device as otpAttempt:
{
"input": {
"otpSessionChallenge": "eca010be19a80de5c134c324af24c36f",
"otpAttempt": "788143"
}
}Example:
$ curl 'https://api.sorare.com/graphql' \
-H 'content-type: application/json' \
-d '{
"operationName": "SignInMutation",
"variables": { "input": { "otpSessionChallenge": "<YourOTPSessionChallenge>", "otpAttempt": "<YourOTPAttemp>" } },
"query": "mutation SignInMutation($input: signInInput!) { signIn(input: $input) { currentUser { slug jwtToken(aud: \"<YourAud>\") { token expiredAt } } errors { message } } }"
}'
{"data":{"signIn":{"currentUser":{"slug":"<YourSlug>","jwtToken":{"token":"<YourJWTToken>","expiredAt":"..."}},"errors":[]}}}There is no way currently to revoke the token.
Should the Terms & Conditions of Sorare get updated, you might need to accept them before being able to sign in. Please refer to https://sorare.com/terms_and_conditions to read the latest version of Sorare's terms.
You can accept the terms without being signed in by retrieving the tcuToken returned by the failing signIn mutation with must_accept_tcus error:
mutation SignInMutation($input: signInInput!) {
signIn(input: $input) {
currentUser {
slug
jwtToken(aud: "<YourAud>") {
token
expiredAt
}
}
otpSessionChallenge
tcuToken
errors {
message
}
}
}If the tcuToken is set, you can accept the updated Terms & Conditions with the following mutation:
mutation AcceptTermsMutation($input: acceptTermsInput!) {
acceptTerms(input: $input) {
errors {
message
}
}
}And the following variables:
{
"input": {
"acceptTerms": true,
"acceptPrivacyPolicy": true,
"acceptGameRules": true,
"tcuToken": "<YourTcuToken>"
}
}Once terms are accepted, you will be able to sign in again.
With our OAuth API, users can sign-in to your service using their Sorare account, which allows you to request data on their behalf.
In order to use our OAuth API, we need to issue you a Client ID and Secret for your application. You can request one through our Help Center with the following information:
- A unique name for your application
- One or more callback URLs (e.g.,
http://localhost:3000/auth/sorare/callbackfor development &https://myapp.com/auth/sorare/callbackfor production) - A logo for your application in PNG format
Once we validate your application, you will be provided with:
- OAuth Client ID
- OAuth Secret (keep this secret!)
All OAuth applications are provided with one scope which allows access to the following:
- Basic user information, including their nickname, avatar, and wallet address
- User's cards, achievements and favorites
- User's auctions, offers and notifications
The following are not accessible:
- Email addresses
- Future lineups and rewards
- Claiming rewards
- Bidding, selling, or making offers cards
- Accepting offers or initiating withdrawals
First you need to create a "Login with Sorare" link in your app and use the following href:
https://sorare.com/oauth/authorize?client_id=<YourUID>&redirect_uri=<YourURLEncodedCallbackURI>&response_type=code&scope=
Once signed in to Sorare, the user will be asked to authorize your app and will ultimately be redirected to your redirect_uri with a ?code= query parameter, for instance https://myapp.com/auth/sorare/callback?code=<YourCode>.
To request an OAuth access token you can then call the https://api.sorare.com/oauth/token endpoint with the following parameters:
client_id=<YourOAuthUID>client_secret=<YourOAuthSecret>code=<TheRetrievedCode>grant_type=authorization_coderedirect_uri=<TheSameCallbackURIAsBefore>
Example:
$ curl -X POST "https://api.sorare.com/oauth/token" \
-H 'content-type: application/x-www-form-urlencoded' \
-d 'client_id=<YourOAuthUID>&client_secret=<YourOAuthSecret>&code=<TheRetrievedCode>&grant_type=authorization_code&redirect_uri=<TheSameCallbackURIAsBefore>'
{"access_token":"....","token_type":"Bearer","expires_in":7200,"refresh_token":"...","scope":"public","created_at":1639608238}You can then use the access_token the same way you would use a JWT token:
curl 'https://api.sorare.com/graphql' \
-H 'content-type: application/json' \
-H 'Authorization: Bearer <TheUserAccessToken>' \
-d '{
"operationName": "CurrentUserQuery",
"query": "query CurrentUserQuery { currentUser { slug } }"
}'
{"data":{"currentUser":{"slug":"<ASlug>"}}}The GraphQL API is rate limited. We can provide an extra API Key on demand that raises those limits.
Here are the configured limits:
- Unauthenticated API calls: 20 calls per minute
- Authenticated (JWT or OAuth) API calls: 60 calls per minute
- API Key API calls: 600 calls per minute
The API key should be passed in an http APIKEY header.
Example:
curl 'https://api.sorare.com/graphql' \
-H 'content-type: application/json' \
-H 'APIKEY: <YourPrivateAPIKey>' \
-H 'Authorization: Bearer <TheUserAccessToken>' \
-d '{
"operationName": "CurrentUserQuery",
"query": "query CurrentUserQuery { currentUser { slug } }"
}'Whenever you perform too many requests, the GraphQL API will answer with a 429 HTTP error code and add a Retry-After: <TimeToWaitInSeconds> header (see RFC) to the response so your code can rely on it to understand how long it should wait before retrying.
The GraphQL queries have complexity and depth limits. We can provide extra API keys (on demand) raising those limits.
Depth reflects the longest nested fields chain.
Complexity reflects the potential total number of fields that would be returned. If the query asks for the first 50 cards, the complexity is computed on 50 cards, even if the result set is composed of 1 card.
Anonymous calls have the following limits:
- depth: 2
- complexity: 500
Authenticated calls (with a JWT Token or an API key) have the following limits:
- depth 10
- complexity: 5000
Our GraphQL API cannot be called from the browser on another domain than the ones we support. Therefore, it's expected to get a Blocked by CORS policy [...]: The ‘Access-Control-Allow-Origin’ header has a value [...] error.
Please consider calling the API from your backend servers.
A common use case in GraphQL is traversing the relationship between sets of objects. There are a number of different ways that these relationships can be exposed in GraphQL, giving a varying set of capabilities to the client developer.
Read more about GraphQL pagination on their official documentation.
At Sorare, we use both plural types for connections with a limited cardinality and cursor-based pagination for the others.
A working JavaScript code sample demonstrating how to leverage the cursor to iterate on all cards of a single user is available in examples/allCardsFromUser.js.
Every operation that involves card or ETH transfer must be signed with your Starkware private key. It can be exported from sorare.com using your wallet.
Make sure to keep your Private Key secret.
To sign with your Starkware private key in JavaScript, we recommend using the JavaScript package @sorare/crypto.
To list the latest auctions, you can use the following query:
query ListLast10EnglishAuctions {
transferMarket {
englishAuctions(last: 10) {
nodes {
slug
currentPrice
endDate
bestBid {
amount
bidder {
... on User {
nickname
}
}
}
minNextBid
cards {
slug
name
rarity
}
}
}
}
}A working JavaScript code sample is available in examples/listEnglishAuctions.js.
To make a bid on an auction, you'll need multiple prerequisites:
- the GraphQL API needs to be called authenticated (see above how to get an Authorization
token) - your Starkware private key
- the
slugof the auction you want to bid for - the
amount(in wei) you want to bid
Here are the few steps required to bid:
-
Retrieve your
starkKeyusing thecurrentUserquery:query CurentUserQuery { currentUser { starkKey } }
-
Get the
id,blockchainId, andminNextBidof the auction you want to bid for:query EnglishAuctionLimitOrder($auctionSlug: String!) { englishAuction(slug: $auctionSlug) { id blockchainId minNextBid } }
-
Get the list of
LimitOrderobjects from theprepareBidmutation on the auction you want to bid for, with the amount you want to bid:const prepareBidInput = { englishAuctionId: englishAuctionBlockchainId, bidAmountWei: bidAmountInWei, };
mutation PrepareBid($input: prepareBidInput!) { prepareBid(input: $input) { limitOrders { vaultIdSell vaultIdBuy amountSell amountBuy tokenSell tokenBuy nonce expirationTimestamp } } }
-
Sign all
LimitOrderobjects and build thebidInputargument.const starkSignatures = limitOrders.map((limitOrder) => ({ data: JSON.stringify(signLimitOrder(privateKey, limitOrder)), nonce: limitOrder.nonce, expirationTimestamp: limitOrder.expirationTimestamp, starkKey, })); const bidInput = { starkSignatures, auctionId: englishAuctionId, amount: bidAmountInWei, clientMutationId: crypto.randomBytes(8).join(""), };
Note that the
clientMutationIdis using a random ID. -
Call the
bidmutation:mutation Bid($input: bidInput!) { bid(input: $input) { bid { id } errors { message } } }
A working JavaScript code sample is available in examples/bidAuctionWithEth.js.
To create a Direct, Single Sale or Single Buy offer, you'll need multiple prerequisites:
- the GraphQL API needs to be called authenticated (see above how to get an Authorization
token) - your Starkware private key
- the
assetIdof the card you want to send (and/or the amount of ETH you want to send) - the list of card
assetIdsyou want to receive in return (and/or the amount of ETH you want to receive)
Here are the few steps required to create an offer:
-
Retrieve your
starkKeyusing thecurrentUserquery:query CurentUserQuery { currentUser { starkKey } }
-
Build the
prepareOfferInputargument:const prepareOfferInput = { type: "SINGLE_SALE_OFFER", sendAssetIds: [tokenAssetId], receiveAssetIds: [], sendWeiAmount: "0", receiveWeiAmount: aWeiAmountAsString, receiverSlug: null, clientMutationId: crypto.randomBytes(8).join(""), };
-
Get the list of
LimitOrderobjects from thelimitOrdersfield of theprepareOffermutation:mutation NewOfferLimitOrders($input: prepareOfferInput!) { prepareOffer(input: $input) { limitOrders { amountBuy amountSell expirationTimestamp feeInfo { feeLimit tokenId sourceVaultId } nonce tokenBuy tokenSell vaultIdBuy vaultIdSell } errors { message } } }
-
Sign all
LimitOrderobjects and build thecreateSingleSaleOfferInputargument.const starkSignatures = limitOrders.map((limitOrder) => ({ data: JSON.stringify(signLimitOrder(privateKey, limitOrder)), nonce: limitOrder.nonce, expirationTimestamp: limitOrder.expirationTimestamp, starkKey, })); const createSingleSaleOfferInput = { starkSignatures, dealId: crypto.randomBytes(8).join(""), assetId: aCardAssetId, price: aWeiAmountAsString, clientMutationId: crypto.randomBytes(8).join(""), };
Note that the
clientMutationIdanddealIdare using random IDs. -
Call the
createSingleSaleOffer(orcreateDirectOfferorcreateSingleBuyOffer) mutation:mutation CreateSingleSaleOffer($input: createSingleSaleOfferInput!) { createSingleSaleOffer(input: $input) { offer { id } errors { message } } }
A working JavaScript code sample is available in examples/createSingleSaleOffer.js.
To accept a Direct, Single Sale or Single Buy offer, you'll need multiple prerequisites:
- the GraphQL API needs to be called authenticated (see above how to get an Authorization
token) - your Starkware private key
- the
idof the offer you want to accept
Here are the few steps required to create an offer:
-
Retrieve your
starkKeyusing thecurrentUserquery:query CurentUserQuery { currentUser { starkKey } }
-
Get the
blockchainIdof the Offer:query GetOffer($id: String!) { transferMarket { offer(id: $id) { blockchainId } } }
-
Build the
prepareAcceptOfferInputargument:const prepareAcceptOfferInput = { dealId: blockchainId, };
-
Get the list of
LimitOrderobjects from thelimitOrdersfield of theprepareAcceptOffermutation:mutation PrepareAcceptOffer($input: prepareAcceptOfferInput!) { prepareAcceptOffer(input: $input) { limitOrders { amountBuy amountSell expirationTimestamp feeInfo { feeLimit tokenId sourceVaultId } id nonce tokenBuy tokenSell vaultIdBuy vaultIdSell } errors { message } } }
-
Sign all
LimitOrderobjects and build theacceptOfferInputargument.const starkSignatures = limitOrders.map((limitOrder) => ({ data: JSON.stringify(signLimitOrder(privateKey, limitOrder)), nonce: limitOrder.nonce, expirationTimestamp: limitOrder.expirationTimestamp, starkKey, })); const acceptOfferInput = { starkSignatures, blockchainId: offer["blockchainId"], clientMutationId: crypto.randomBytes(8).join(""), };
Note that the
clientMutationIdis using a random ID. -
Call the
acceptOffermutation:mutation AcceptSingleSaleOffer($input: acceptOfferInput!) { acceptOffer(input: $input) { offer { id } errors { message } } }
A working JavaScript code sample is available in examples/acceptSingleSaleOffer.js.
To query the Sorare: MLB or Sorare: NBA Graphql API ensure you target the https://api.sorare.com/sports/graphql endpoint. For instance to get a BaseballCard from their assetId, you can use:
const slugs = [slug1, slug2];query GetBaseballCardByAssetId($slugs: [String!]) {
baseballCards(slugs: $slugs) {
assetId
slug
rarity
season
serialNumber
positions
team {
name
}
player {
displayName
}
}
}A working JavaScript code sample is available in examples/getBaseballCard.js.
const slugs = [slug1, slug2];query GetNBACardByAssetId($slugs: [String!]) {
nbaCards(slugs: $slugs) {
assetId
slug
rarity
season
serialNumber
positions
team {
name
}
player {
displayName
}
}
}Until both GraphQL APIs are federated, you have to perform two API calls. The first one fetches the assetIds of the cards, and the second one fetches the price for each token.
A working JavaScript code sample is available in examples/getNBACardPrice.js.
First, you have to fetch the assetIds from the Sorare: MLB/NBA API.
const slugs = [slug1, slug2];query GetNBACardsAssetIds($slugs: [String!]) {
nbaCards(slugs: $slugs) {
assetId
}
}Once you have the assetIds of the tokens you are interested in, you can call the Sorare API:
query GetNBACardsPrices($assetIds: [String!]!) {
tokens {
nfts(
assetIds: $assetIds
) {
latestEnglishAuction {
bestBid {
amount
amountInFiat { eur gbp usd }
}
}
}
}
}A working JavaScript code sample is available in examples/getNBACard.js.
The Sorare API provides different GraphQL events to subscribe to:
aCardWasUpdated: triggers every time a football card is updated. This can be filtered using the following arguments:ages,cardEditions,playerSlugs,positions,owned,rarities,seasonStartYears,serialNumbers,shirtNumbers,slugsbundledAuctionWasUpdated: triggers every time a (bundled) english auction is updatedcurrentUserWasUpdated: scoped to the current user, triggers every time the current user is updated (only works when authenticated)gameWasUpdated: triggers every time a game is updatedofferWasUpdated: scoped to the received and the sender of the offer, triggers every time an offer is updated (only works when authenticated with the sender or the receiver)publicMarketWasUpdated: triggers every time a card is updated on a public market (auction and single sale offers): on a bid, when an auction ends, when a single sale offer is accepted.tokenAuctionWasUpdated: triggers every time an auction is updated (football,baseball&nbacollections)tokenOfferWasUpdated: triggers every time an offer is updated (football,baseball&nbacollections)
The websocket URL to use is wss://ws.sorare.com/cable.
Sorare's GraphQL subscriptions are implemented through websockets with the actioncable-v1-json sub-protocol. Sorare relies on ActionCable because the sorare.com website has been scaled on a Ruby on Rails stack.
In order to ease the websocket + actioncable-v1-json sub-protocoal usage outside of a Ruby on Rails environment, you can use the TypeScript/JavaScript package @sorare/actioncable:
$ yarn add @sorare/actioncableconst { ActionCable } = require("@sorare/actioncable");
const cable = new ActionCable({
headers: {
// 'Authorization': `Bearer <YourJWTorOAuthToken>`,
// 'APIKEY': '<YourOptionalAPIKey>'
},
});
cable.subscribe("aCardWasUpdated { slug }", {
connected() {
console.log("connected");
},
disconnected(error) {
console.log("disconnected", error);
},
rejected(error) {
console.log("rejected", error);
},
received(data) {
const aCardWasUpdated = data?.result?.data?.aCardWasUpdated;
if (!aCardWasUpdated) {
return;
}
const { id } = aCardWasUpdated;
console.log("a card was updated", id);
},
});A working JavaScript code sample is available in examples/subscribeAllCardUpdates.js.
Example of GraphQL subscription to get notified each time an offer is updated:
subscription {
tokenOfferWasUpdated {
status
actualReceiver {
... on User {
slug
}
}
sender {
... on User {
slug
}
}
senderSide {
wei
fiat {
eur
usd
gbp
}
nfts {
assetId
collectionName
}
}
receiverSide {
wei
fiat {
eur
usd
gbp
}
nfts {
assetId
collectionName
metadata {
... on TokenCardMetadataInterface {
playerSlug
rarity
serialNumber
}
}
}
}
}
}Example of GraphQL subscription to get notified each time an auction is updated:
subscription {
tokenAuctionWasUpdated {
open
bestBid {
amount
amountInFiat {
eur
usd
gbp
}
bidder {
... on User {
slug
}
}
}
bids {
nodes {
amount
amountInFiat {
eur
usd
gbp
}
bidder {
... on User {
slug
}
}
}
}
nfts {
assetId
collectionName
metadata {
... on TokenCardMetadataInterface {
playerSlug
rarity
serialNumber
}
}
}
}
}A working JavaScript code sample is available in examples/subscribeTokenWasUpdated.js.
$ pip3 install websocket-clientimport websocket
import json
import time
w_socket = 'wss://ws.sorare.com/cable'
identifier = json.dumps({"channel": "GraphqlChannel"})
subscription_query = {
"query": "subscription onAnyCardUpdated { aCardWasUpdated { slug } }",
"variables": {},
"operationName": "onAnyCardUpdated",
"action": "execute"
}
def on_open(ws):
subscribe_command = {"command": "subscribe", "identifier": identifier}
ws.send(json.dumps(subscribe_command).encode())
time.sleep(1)
message_command = {
"command": "message",
"identifier": identifier,
"data": json.dumps(subscription_query)
}
ws.send(json.dumps(message_command).encode())
def on_message(ws, data):
message = json.loads(data)
type = message.get('type')
if type == 'welcome':
pass
elif type == 'ping':
pass
elif message.get('message') is not None:
print(message['message'])
def on_error(ws, error):
print('Error:', error)
def on_close(ws, close_status_code, close_message):
print('WebSocket Closed:', close_message, close_status_code)
def long_connection():
ws = websocket.WebSocketApp(
w_socket,
on_message=on_message,
on_close=on_close,
on_error=on_error,
on_open=on_open
)
ws.run_forever()
if __name__ == '__main__':
long_connection()A working Python3 code sample is available in examples/subscribe_all_card_updates.py.
Using the gqlactioncable package, it is now possible to make subscriptions using graphql-python/gql.
$ pip install gqlactioncableimport asyncio
from gql import Client, gql
from gqlactioncable import ActionCableWebsocketsTransport
async def main():
transport = ActionCableWebsocketsTransport(
url="wss://ws.sorare.com/cable",
)
async with Client(transport=transport) as session:
subscription = gql(
"""
subscription onAnyCardUpdated {
aCardWasUpdated {
slug
}
}
"""
)
async for result in session.subscribe(subscription):
print(result)
asyncio.run(main())This example is available in examples/gql_subscription_all_cards.py.
See also an example for http queries with gql: examples/gql_query_all_cards.py.

