Process Stroke Data
Process handwriting stroke coordinates directly, without needing to generate an image first. Supports the same options as image processing but with lower latency.
Send stroke data
Send stroke data to the v3/strokes endpoint. Strokes are represented as arrays of x and y coordinates, where each sub-array is one continuous stroke.
The request body has a double-nested strokes key:
- The outer
strokesis the API parameter name - The inner
strokescontains the coordinate data (xandyarrays)
The example below sends four strokes that form the handwritten expression :
x and y coordinate arrays.- Request body
- cURL
- Python
- JavaScript / TypeScript
- Go
- Java
{
"strokes": {
"strokes": {
"x": [
[131,131,130,130,131,133,136,146,151,158,161,162,162,162,162,159,155,147,142,137,136,138,143,160,171,190,197,202,202,202,201,194,189,177,170,158,153,150,148],
[231,231,233,235,239,248,252,260,264,273,277,280,282,283],
[273,272,271,270,267,262,257,249,243,240,237,235,234,234,233,233],
[296,296,297,299,300,301,301,302,303,304,305,306,306,305,304,298,294,286,283,281,281,282,284,284,285,287,290,293,294,299,301,308,309,314,315,316]
],
"y": [
[213,213,212,211,210,208,207,206,206,209,212,217,220,227,230,234,236,238,239,239,239,239,239,239,241,247,252,259,261,264,266,269,270,271,271,271,270,269,268],
[231,231,232,235,238,246,249,257,261,267,270,272,273,274],
[230,230,230,231,234,240,246,258,268,273,277,281,281,283,283,284],
[192,192,191,189,188,187,187,187,188,188,190,193,195,198,200,205,208,213,215,215,215,214,214,214,214,216,218,220,221,223,223,223,223,221,221,220]
]
}
}
}
curl -X POST https://api.mathpix.com/v3/strokes \
-H 'app_id: APP_ID' \
-H 'app_key: APP_KEY' \
-H 'Content-Type: application/json' \
--data '{ "strokes": {"strokes": {
"x": [[131,131,130,130,131,133,136,146,151,158,161,162,162,162,162,159,155,147,142,137,136,138,143,160,171,190,197,202,202,202,201,194,189,177,170,158,153,150,148],[231,231,233,235,239,248,252,260,264,273,277,280,282,283],[273,272,271,270,267,262,257,249,243,240,237,235,234,234,233,233],[296,296,297,299,300,301,301,302,303,304,305,306,306,305,304,298,294,286,283,281,281,282,284,284,285,287,290,293,294,299,301,308,309,314,315,316]],
"y": [[213,213,212,211,210,208,207,206,206,209,212,217,220,227,230,234,236,238,239,239,239,239,239,239,241,247,252,259,261,264,266,269,270,271,271,271,270,269,268],[231,231,232,235,238,246,249,257,261,267,270,272,273,274],[230,230,230,231,234,240,246,258,268,273,277,281,281,283,283,284],[192,192,191,189,188,187,187,187,188,188,190,193,195,198,200,205,208,213,215,215,215,214,214,214,214,216,218,220,221,223,223,223,223,221,221,220]]
}}}'
import requests, json
strokes_data = {
"strokes": {
"x": [
[131,131,130,130,131,133,136,146,151,158,161,162,162,162,162,159,155,147,142,137,136,138,143,160,171,190,197,202,202,202,201,194,189,177,170,158,153,150,148],
[231,231,233,235,239,248,252,260,264,273,277,280,282,283],
[273,272,271,270,267,262,257,249,243,240,237,235,234,234,233,233],
[296,296,297,299,300,301,301,302,303,304,305,306,306,305,304,298,294,286,283,281,281,282,284,284,285,287,290,293,294,299,301,308,309,314,315,316]
],
"y": [
[213,213,212,211,210,208,207,206,206,209,212,217,220,227,230,234,236,238,239,239,239,239,239,239,241,247,252,259,261,264,266,269,270,271,271,271,270,269,268],
[231,231,232,235,238,246,249,257,261,267,270,272,273,274],
[230,230,230,231,234,240,246,258,268,273,277,281,281,283,283,284],
[192,192,191,189,188,187,187,187,188,188,190,193,195,198,200,205,208,213,215,215,215,214,214,214,214,216,218,220,221,223,223,223,223,221,221,220]
]
}
}
r = requests.post("https://api.mathpix.com/v3/strokes",
json={"strokes": strokes_data},
headers={
"app_id": "APP_ID",
"app_key": "APP_KEY",
"Content-type": "application/json"
}
)
print(json.dumps(r.json(), indent=4, sort_keys=True))
const response = await fetch("https://api.mathpix.com/v3/strokes", {
method: "POST",
headers: {
app_id: "APP_ID",
app_key: "APP_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
strokes: {
strokes: {
x: [
[131,131,130,130,131,133,136,146,151,158,161,162,162,162,162,159,155,147,142,137,136,138,143,160,171,190,197,202,202,202,201,194,189,177,170,158,153,150,148],
[231,231,233,235,239,248,252,260,264,273,277,280,282,283],
[273,272,271,270,267,262,257,249,243,240,237,235,234,234,233,233],
[296,296,297,299,300,301,301,302,303,304,305,306,306,305,304,298,294,286,283,281,281,282,284,284,285,287,290,293,294,299,301,308,309,314,315,316],
],
y: [
[213,213,212,211,210,208,207,206,206,209,212,217,220,227,230,234,236,238,239,239,239,239,239,239,241,247,252,259,261,264,266,269,270,271,271,271,270,269,268],
[231,231,232,235,238,246,249,257,261,267,270,272,273,274],
[230,230,230,231,234,240,246,258,268,273,277,281,281,283,283,284],
[192,192,191,189,188,187,187,187,188,188,190,193,195,198,200,205,208,213,215,215,215,214,214,214,214,216,218,220,221,223,223,223,223,221,221,220],
],
},
},
}),
});
const result = await response.json();
console.log(JSON.stringify(result, null, 2));
body := bytes.NewBufferString(`{
"strokes": {"strokes": {
"x": [[131,131,130,130,131,133,136,146,151,158,161,162,162,162,162,159,155,147,142,137,136,138,143,160,171,190,197,202,202,202,201,194,189,177,170,158,153,150,148],[231,231,233,235,239,248,252,260,264,273,277,280,282,283],[273,272,271,270,267,262,257,249,243,240,237,235,234,234,233,233],[296,296,297,299,300,301,301,302,303,304,305,306,306,305,304,298,294,286,283,281,281,282,284,284,285,287,290,293,294,299,301,308,309,314,315,316]],
"y": [[213,213,212,211,210,208,207,206,206,209,212,217,220,227,230,234,236,238,239,239,239,239,239,239,241,247,252,259,261,264,266,269,270,271,271,271,270,269,268],[231,231,232,235,238,246,249,257,261,267,270,272,273,274],[230,230,230,231,234,240,246,258,268,273,277,281,281,283,283,284],[192,192,191,189,188,187,187,187,188,188,190,193,195,198,200,205,208,213,215,215,215,214,214,214,214,216,218,220,221,223,223,223,223,221,221,220]]
}}
}`)
req, _ := http.NewRequest("POST", "https://api.mathpix.com/v3/strokes", body)
req.Header.Set("app_id", "APP_ID")
req.Header.Set("app_key", "APP_KEY")
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
result, _ := io.ReadAll(resp.Body)
fmt.Println(string(result))
HttpClient client = HttpClient.newHttpClient();
String body = """
{
"strokes": { "strokes": {
"x": [[131,131,130,130,131,133,136,146,151,158,161,162,162,162,162,159,155,147,142,137,136,138,143,160,171,190,197,202,202,202,201,194,189,177,170,158,153,150,148],[231,231,233,235,239,248,252,260,264,273,277,280,282,283],[273,272,271,270,267,262,257,249,243,240,237,235,234,234,233,233],[296,296,297,299,300,301,301,302,303,304,305,306,306,305,304,298,294,286,283,281,281,282,284,284,285,287,290,293,294,299,301,308,309,314,315,316]],
"y": [[213,213,212,211,210,208,207,206,206,209,212,217,220,227,230,234,236,238,239,239,239,239,239,239,241,247,252,259,261,264,266,269,270,271,271,271,270,269,268],[231,231,232,235,238,246,249,257,261,267,270,272,273,274],[230,230,230,231,234,240,246,258,268,273,277,281,281,283,283,284],[192,192,191,189,188,187,187,187,188,188,190,193,195,198,200,205,208,213,215,215,215,214,214,214,214,216,218,220,221,223,223,223,223,221,221,220]]
}}
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.mathpix.com/v3/strokes"))
.header("app_id", "APP_ID")
.header("app_key", "APP_KEY")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
{
"request_id": "cea6b8e4-0ab4-550a-c467-ce2eb00430be",
"is_printed": false,
"is_handwritten": true,
"auto_rotate_confidence": 0.0020149118193977245,
"auto_rotate_degrees": 0,
"confidence": 1,
"confidence_rate": 1,
"latex_styled": "3 x^{2}",
"text": "\\( 3 x^{2} \\)",
"version": "SuperNet-109p4"
}
In the example response, the latex_styled field renders as:
Live stroke sessions
For live digital ink with updating results (e.g., in a mobile app), use app tokens with stroke sessions:
- Get an
app_tokenwithstrokes_session_idfrom your server:
{
"include_strokes_session_id": true,
"expires": 300
}
- Use the
app_tokenandstrokes_session_idin client requests to v3/strokes.
Live stroke sessions are billed differently from standalone requests — see pricing. You are billed the first time strokes are sent for a session, not when requesting the token.
Next steps
- v3/strokes reference — Full request parameters and response schema
- Authentication — App tokens for client-side stroke sessions