API
WebSocket
REST gives you the final output. A WebSocket connection gives you output as it appears, and lets your user answer a program while it runs.
It runs both kinds of program:
- A single file. Send the code itself as a
script. - A project with several files. Upload the project once as a zip, then run
it by its
projectKey.
Use it for a terminal, a program that prompts the user, or anything where waiting until the end is not good enough.
Everything about languages, versions and program parameters is the same as REST. What changes is how you connect and how messages arrive.
How the connection is made
Your server holds the secret. The browser holds only a token, and talks to us directly. This is the same for a single file and for a project. A project only adds an upload at the start.
A single file
A project with several files
The details and examples are in "Projects with several files", further down this page.
Your server is only involved in getting the token, and in uploading a project. After that the conversation is between your user and us, which is what makes an interactive program feel immediate.
Get a token
The browser connects to us directly, so your client secret must not be involved. Your server exchanges the secret for a short-lived token first.
curl -X POST 'https://api.jdoodle.com/v1/auth-token' \
-H 'Content-Type: application/json' \
-d '{
"clientId": "your_client_id",
"clientSecret": "your_client_secret"
}'// Java 11 or newer. Runs on your server, never in a browser.
String body = "{"
+ "\"clientId\": \"your_client_id\","
+ "\"clientSecret\": \"your_client_secret\""
+ "}";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.jdoodle.com/v1/auth-token"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());// Node 18 or newer. Runs on your server, never in a browser.
const response = await fetch('https://api.jdoodle.com/v1/auth-token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
clientId: 'your_client_id',
clientSecret: 'your_client_secret'
})
})
const token = await response.text()
// Send this token to the browser. Never the secret.# Runs on your server, never in a browser.
import requests
response = requests.post(
"https://api.jdoodle.com/v1/auth-token",
json={
"clientId": "your_client_id",
"clientSecret": "your_client_secret",
},
)
token = response.textThe response is the token itself, as plain text rather than JSON:
eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJKRE9PRExFIiwic3ViIjoiV1MtQVBJLVRPS0VOIi...The token lasts three minutes, and you can run as many programs as you like in that time. Getting one costs one credit, on top of the credit each run costs, so one token covering a whole session is cheaper than one per run. See credits and pricing.
Request a token when a user is about to run something, rather than fetching one and keeping it.
Connect
https://api.jdoodle.com/v1/stompThe connection speaks STOMP over SockJS, so use a SockJS client with a STOMP wrapper rather than a raw WebSocket. Load JDoodle's SockJS build:
<script src="https://api.jdoodle.com/js/sockjs.js"></script>
<script src="https://api.jdoodle.com/js/webstomp.js"></script>
The examples below are JavaScript because this half runs in your user's browser. Your server's part is getting the token above, which you can do in any language.
| What | Where |
|---|---|
| Subscribe to | /user/queue/execute-i |
| Send messages to | /app/execute-ws-api-token |
// JDoodle's sockjs and webstomp builds, loaded in the browser.
// heartbeat:false is required — webstomp's default heartbeat misbehaves over
// the SockJS transport and tears the connection down before output streams.
const client = webstomp.over(new SockJS('https://api.jdoodle.com/v1/stomp'), {
heartbeat: false
})
client.connect({}, () => {
client.subscribe('/user/queue/execute-i', (message) => {
const status = Number(message.headers.statusCode)
if (status === 201) console.log('started')
else if (status === 204) console.log('finished')
else console.log(message.body)
})
client.send(
'/app/execute-ws-api-token',
JSON.stringify({
script: 'print("Hello, World!")',
language: 'python3',
// versionIndex must be a number, not a string
versionIndex: 0
}),
{ message_type: 'execute', token }
)
})Every message carries two headers:
token. The token your server obtained.message_type. Eitherexecuteto start a program, orinputto answer one that is running.
Start a program
Send a message_type of execute, with the same fields REST takes:
{
"script": "name = input()\nprint(f\"Hello, {name}\")",
"language": "python3",
"versionIndex": 0
}Answer a running program
Send a message_type of input with what your user typed. The body is the
text itself rather than JSON, and a newline is \n.
client.send('/app/execute-ws-api-token', userTyped + '\n', {
message_type: 'input',
token
})This is the part REST cannot do. You do not need to know the input before the program starts.
Projects with several files
The socket can run a project with several files, but it cannot carry the files themselves. Running one takes two steps:
- Your server uploads the project as a zip over REST and gets back a
projectKey. - The browser sends an
executemessage with that key in place of ascript.
The zip rules are the same as for REST. See "The project must be a zip" in REST.
Upload the project
Upload with execute-api-multifile-upload-only. It stores the project without
running it, so nothing runs until your user is ready. This request carries your
client secret, so it belongs on your server, next to the token request.
The upload is a form upload, not JSON. The file must be sent with the content
type application/zip. Some clients do not set this on their own, so the
examples below set it.
curl -X POST 'https://api.jdoodle.com/v1/execute-api-multifile-upload-only' \
-F 'clientId=your_client_id' \
-F 'clientSecret=your_client_secret' \
-F 'projectFile=@project.zip;type=application/zip'// Java 11 or newer. Runs on your server, never in a browser.
// Multipart needs a boundary, so the body is built by hand.
String boundary = "----jdoodle" + System.currentTimeMillis();
var parts = new StringBuilder();
for (String[] field : new String[][] {
{ "clientId", "your_client_id" },
{ "clientSecret", "your_client_secret" }
}) {
parts.append("--").append(boundary).append("\r\n")
.append("Content-Disposition: form-data; name=\"").append(field[0]).append("\"\r\n\r\n")
.append(field[1]).append("\r\n");
}
byte[] project = Files.readAllBytes(Path.of("project.zip"));
var body = new ByteArrayOutputStream();
body.write(parts.toString().getBytes());
body.write(("--" + boundary + "\r\nContent-Disposition: form-data; "
+ "name=\"projectFile\"; filename=\"project.zip\"\r\n"
+ "Content-Type: application/zip\r\n\r\n").getBytes());
body.write(project);
body.write(("\r\n--" + boundary + "--\r\n").getBytes());
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.jdoodle.com/v1/execute-api-multifile-upload-only"))
.header("Content-Type", "multipart/form-data; boundary=" + boundary)
.POST(HttpRequest.BodyPublishers.ofByteArray(body.toByteArray()))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());// Node 18 or newer. Runs on your server, never in a browser.
import { readFile } from 'node:fs/promises'
const zip = new Blob([await readFile('project.zip')], { type: 'application/zip' })
const form = new FormData()
form.set('clientId', 'your_client_id')
form.set('clientSecret', 'your_client_secret')
form.set('projectFile', zip, 'project.zip')
const response = await fetch(
'https://api.jdoodle.com/v1/execute-api-multifile-upload-only',
{ method: 'POST', body: form }
)
const { projectKey } = await response.json()
// Send this key to the browser, together with the token.# Runs on your server, never in a browser.
import requests
with open("project.zip", "rb") as project:
response = requests.post(
"https://api.jdoodle.com/v1/execute-api-multifile-upload-only",
data={
"clientId": "your_client_id",
"clientSecret": "your_client_secret",
},
files={"projectFile": ("project.zip", project, "application/zip")},
)
project_key = response.json()["projectKey"]The response is JSON. Only projectKey is filled in, because nothing ran:
{
"projectKey": "your_client_id-75845496-87a1-428e-891c-dc5697014fdb",
"output": null,
"statusCode": null
}Give the browser the projectKey and a token. Neither one is a secret, and the
key only works for the client ID that uploaded it.
Run it over the socket
Send a message_type of execute as before. In place of script, send:
| Field | What it is |
|---|---|
multiFile |
true. Without it, the key is ignored and nothing runs |
projectKey |
The key from the upload |
mainFile |
The file that should run, by its path inside the zip |
language |
Which language to run it in |
versionIndex |
Position in the version list, as a number |
client.send(
'/app/execute-ws-api-token',
JSON.stringify({
multiFile: true,
projectKey,
mainFile: 'Main.java',
language: 'java',
versionIndex: 0
}),
{ message_type: 'execute', token }
)Everything after that is the same as a single file. Output arrives on
/user/queue/execute-i, and you answer the program with input messages.
You can run the same key as many times as you like while it lasts. Send
mainFile every time, because it is not stored with the project. Without it,
the run fails with status 503.
How long a project lasts
A project is kept for 30 minutes after it is uploaded. Runs over the socket do not extend this. When the time is up, upload the project again to get a new key.
If the key has expired, was never uploaded, or belongs to another client ID, the run does not start. You get a message with status 404 that names the key.
What it costs
Uploading costs one credit. Each run of a project over the socket costs two credits, plus one more if internet access is on. The token costs one credit as usual. See credits and pricing.
What comes back
Output arrives as messages on the queue you subscribed to. The status on each message says what it is:
| Status | Meaning |
|---|---|
| 201 | Execution started |
| 204 | Execution time |
| 206 | Output files |
| 400, 401 | Authentication problem |
| 404 | Project key not found |
| 429 | Daily limit reached |
| 410 | Connection closed (run ended) |
| 500 | Server error |
A working sample
A complete HTML page that connects, runs a program, and lets you answer it while it runs. Save it, open it in a browser, paste a token, and press Run.
Download jdoodle-websocket.html
It is one file with no build step. The two libraries load from
api.jdoodle.com/js/, so the downloaded page runs on its own, and the whole
flow is about eighty lines, so it is easier to read than to describe.
When REST is the better choice
REST is less to build and less to get wrong. Choose the socket only when your users need to interact with a running program, or when output arrives over time and they should see it as it happens.
What next
Whichever interface you use, the language and version values are the same.