Skip to content

Developer Guide

This guide is provided to make it easier for developers to implement clients for MJAI Server. All public API endpoints and their request and response schema can be found at https://mjai-doc.7xcnnw11phu.eu.org. You can find the API server host names on that page and try API endpoints out to verify your understanding of the flows before actually implement your own clients.

Authentication

The first step is to authenticate your clients, so that you can access the API endpoints requiring authentication.

If you have a normal account, you can obtain a session token by requesting /user/login with your username and secret.

If you want to log in as a temporary user, you can get a session token by requesting /user/trial with a valid trial code. It is recommended that the users have fixed IP addresses, because the session tokens will be invalid if users have different IPs in other connections.

Then, attach your session token in the authentication HTTP header for all API endpoints requiring authentication, where the format should be Authorization: Bearer <token>.

Mjai Communication

Before sending Mjai events to the API server, you need to initialize the bot by /mjai/start. Then, you can update the game states and receive bot actions by /mjai/act and /mjai/batch. After you have completed using the bot, you can request /mjai/stop, so that the bot will become uninitialized. It can reduce the likelihood that you leave an undiscovered bug in your clients, sending Mjai events without properly initializing the bot after previous usage.

For details, please refer to the Mjai Protocol for 4-Player page for 4-player bots and the Mjai Protocol for 3-Player page for the minor differences in 3-player bots from 4-player ones.

Getting Discard Tiles at Riichi

You can refer to this question on the FAQ page: How to get dahai decisions corresponding to reach without actually declaring riichi?

In short, unlike some of existing implementations, MJAI Server makes it convenient for developers to get discard tiles (and maybe their rankings if metadata are available) if the choice of the tile to discard at riichi is not unique. You can send a crafted reach event to get the discard tiles. As long as you do not send a reach_accepted event, the riichi declaration will simply be ignored at the next tile drawing event (tsumo). There are two cautions: if your clients have sent a crafted reach event, do not send it again if the users or the clients actually decide to riichi, because the reach event can appear in the list of real events again; also, you must ensure that a reach_accepted event is immediately after the discard event to make the riichi valid, unless the discarded tile deals in to other players' hands.

Efficiency of HTTP Communication

You should ensure that your clients reuse existing TCP connections to minimize the latency to the server. For almost all HTTP libraries in various programming languages, they support pooling the connections when they are idle. Normally, you should wait for the server returning responses before making new requests, so the connection pool should only require a single connection. No new TCP connections should be made, unless your requests time out or the connection is closed by the server due to inactivity.

Since Python is a popular choice to implement clients, here is some advice for using the most popular requests library. If you are using requests.get and requests.post directly, they are bad because each call creates a new TCP connection, and the connections are closed when garbage collected. It will result in huge latency, negatively affecting user experiences. The correct way is to create a single session by requests.Session (see Requests Advanced Usage), and then make requests using this session.

A typical example is that you create a session in the __init__ method of a class by this.session = requests.Session(). Then, in other methods of the class, you can make GET requests by this.session.get and POST requests by this.session.post.

If your application is multithreaded or asynchronous, you may need to employ synchronization primitives such as locks to ensure that there is only a single active TCP connection. Using a single session in requests do not guarantee a single TCP connection; it only enables connection reuse. You have to make sure that there are no concurrent requests, so that no other TCP connections need to be created in the pool. Ensuring a single TCP connection can avoid the HTTP status code 429 Too Many Requests, because all requests exceeding the soft rate limit will be delayed processing, making it impossible to exceed the hard rate limit. Otherwise, your clients need to deal with request rate limits gracefully, which can be much more complicated. For details about the rate limits, see this question on the FAQ page: What is the request rate limit?

Implementation References

There have already been open source projects that use MJAI Server, and you can refer to their implementation of the clients.

Mahjong Copilot has support for MJAI Server, named MJAPI. It includes MJAI Server authentication and communication with the modified Mjai protocol, but the client does not support TCP connection reuse for HTTP requests. You should make modifications according to the previous recommendations.