API
Save a log from a launcher, mod or server panel with one request. You get a link back.
Quick start
Send the log text to https://api.minelog.org and you get a link back. Sending it as plain text means there's nothing to escape, which suits shell scripts and Java mods.
curl --data-binary @latest.log \
-H "Content-Type: text/plain" \
"https://api.minelog.org/v2/logs?source=MyLauncher"A successful upload returns 201 and the details of the saved log:
{
"id": "k7M2xq9D4",
"url": "https://minelog.org/k7M2xq9D4",
"raw": "https://api.minelog.org/v2/logs/k7M2xq9D4/raw",
"source": "MyLauncher",
"kind": "server",
"lines": 1204,
"size": 88012,
"errors": 3,
"warnings": 11,
"privacyApplied": true,
"createdAt": "2026-09-24T10:40:55Z",
"expiresAt": "2026-11-03T10:40:55Z"
}Open url to see the log. That's the link you give to a player or paste into a support thread.
Basics
Everything is on https://api.minelog.org, separate from the website, so a launcher only needs one short host. Logs are still read on https://minelog.org, and every url in a response points there.
There are two protocols on the same host. /v2 is ours, and it's the one to use for new work. /1 copies the mclo.gs API, so tools written for it work once you change the host. Both use the same storage: a log saved through one opens through the other.
- Requests and responses are UTF-8.
/v2dates are ISO 8601 in UTC. - You can gzip large uploads with
Content-Encoding: gzip. The size limits apply to the unpacked text. - Every endpoint allows cross-origin requests, so a web page can upload straight from the browser.
- There are no accounts and no API keys. Limits are per connection.
Limits
These are the same limits the website uses, so anything that uploads there uploads here.
- Largest log
- 15 MBMeasured on the text that gets saved, after private details are hidden. With gzip, the unpacked size is what counts.
- Most lines
- 30,000A log with one very long line counts as one line. The size limit still applies to it.
- Kept for
- 40 daysEvery log deletes itself after this long. You can't extend it or delete a log early.
- Uploads
- 30 per hourPer connection. Reading logs doesn't count.
Instead of hard coding these numbers, read them from /v2/limits.
Identify your app
Send your app's name as source with each upload. The log page shows it as a small badge, so a player who opens a link from your app can see it came from there. Any name works, there is nothing to sign up for and no list to be on. Every name is shown exactly as you send it, and none is treated differently from another.
- Up to 32 characters.
- Letters and digits from any language, with spaces or
. _ + -between words. Something likeMyLauncherorMy Launcher 2.1. - A name that doesn't fit is ignored, and the log is saved without a badge. It is never an error.
You can also send it in an X-Minelog-Client header, which helps when you can't change the body.
The source isn't verified. Anyone can send any name, so treat the badge as a hint about where a link came from, not a guarantee.
Private details
By default, minelog hides IP addresses, user folder names, tokens and email addresses before saving. Player names stay, because you need them for debugging. The server does this on its own copy, so a client that skips it still can't store private details.
To turn it off, send hidePrivate: false. The log is then saved as sent, privacyApplied is false in the response, and the log page shows a warning to everyone who opens it. Leave it on unless you have a reason not to.
Save a log
POST/v2/logs
Saves one log and returns its details. You can send it two ways:
- Plain text. Send
Content-Type: text/plainwith the log as the body. Options go in the query string. - JSON. Send
Content-Type: application/jsonwith an object.
| Name | Type | Needed | What it does |
|---|---|---|---|
content | string | required | The log text. In plain text mode you leave this out and the whole request body is the log. Goes in: JSON body. |
hidePrivate | boolean | default true | Hides IP addresses, MAC addresses, user folder names, tokens and emails before saving. The server does it again on its own copy, so a client that skips it can't leak anything. Goes in: JSON body or query. |
source | string | optional | The name of the app that is uploading, like MyLauncher. It shows as a badge on the log page. Names that don't fit the rules above are ignored, not treated as errors. Goes in: JSON body, query or X-Minelog-Client header. |
The response is 201 with the fields below. Keep the id if you want to read the log again later.
| Name | Type | What it does |
|---|---|---|
id | string | Nine characters, case sensitive. Ids can't be guessed. |
url | string | The page where people read the log. |
raw | string | The whole log as plain text. |
source | string | null | The source name as saved, or null if none was sent or it was ignored. |
kind | string | What minelog detected: server, client, crash, jvm, yaml, toml, props, json, readme or unknown. |
lines | number | Number of lines saved. |
size | number | Size of the saved text in bytes. |
errors | number | Number of error entries. A whole stack trace counts once. |
warnings | number | Number of warning entries. |
privacyApplied | boolean | False if hidePrivate was turned off for this log. |
createdAt | string | When it was saved, in ISO 8601 UTC. |
expiresAt | string | When it will be deleted, in ISO 8601 UTC. |
This can fail with 400, 413, 422 or 429. See Errors.
Read a log
GET/v2/logs/{id}
Returns the same fields as an upload. Add ?content=true to include the log text in a content field. Without it, the response stays small no matter how big the log is.
If the id doesn't exist or the log has expired, you get a 404:
{
"error": {
"code": "not_found",
"message": "This log doesn't exist or has expired."
}
}Get the raw text
GET/v2/logs/{id}/raw
Returns the log exactly as saved, as text/plain; charset=utf-8. Useful for piping into other tools. Errors here are still JSON, so check the status code before parsing.
Read the limits
GET/v2/limits
Returns the current limits, so an app can check a file before trying to upload it:
{
"retentionDays": 40,
"maxBytes": 15728640,
"maxLines": 30000,
"uploadsPerHour": 30
}Errors
Every /v2 error has the same shape: an error object with a code you can branch on and a message written for people. Show the message to users as is, but don't match on its wording, because it may change.
{
"error": {
"code": "too_many_lines",
"message": "This log has too many lines. The limit is 30,000."
}
}| Code | Status | Meaning |
|---|---|---|
invalid_body | 400 | The body isn't valid JSON or gzip, or isn't text. |
empty_content | 400 | There's no log text to save. |
not_plain_text | 400 | The content looks like a binary file. Only plain text logs and configs can be shared. |
invalid_option | 400 | An option has the wrong type, like a hidePrivate that isn't true or false. |
not_found | 404 | The log doesn't exist or has expired. |
log_too_large | 413 | The log is over the size limit. |
too_many_lines | 413 | The log is over the line limit. |
blocked_content | 422 | The log contains content we can't host. The message gives the line number and the text around the match, so you know what to remove. |
rate_limited | 429 | Too many uploads from this connection. Retry-After tells you how many seconds to wait. |
server_error | 500 | Something broke on our side. Nothing was saved, so it's safe to try again in a moment. |
Rate limits
Each connection can save 30 logs an hour. After that you get a 429 with a Retry-After header, in seconds. We count using a keyed hash of the IP address and never store the address itself. Reads aren't counted.
An app running on a player's own machine is fine, since each player has their own connection. If your service will upload for many people from one server, contact us before you launch so we can plan for it.
mclo.gs compatibility
If your tool already uploads to mclo.gs, you don't need to rewrite it. Change api.mclo.gs to api.minelog.org and keep the /1 paths:
curl -X POST https://api.mclo.gs/1/log \
--data-urlencode "content@latest.log"The response has the shape those tools expect, with a success flag and times in unix seconds:
{
"success": true,
"id": "k7M2xq9D4",
"source": "MyLauncher",
"created": 1790246455,
"expires": 1793702455,
"size": 88012,
"lines": 1204,
"errors": 3,
"url": "https://minelog.org/k7M2xq9D4",
"raw": "https://api.minelog.org/1/raw/k7M2xq9D4",
"metadata": []
}Errors use the same flat shape rather than the /v2 one:
{
"success": false,
"error": "This log is too large."
}A few things can't match, mostly because minelog never deletes logs early. This is every mclo.gs endpoint and where it stands:
| Endpoint | Status | Notes |
|---|---|---|
POST /1/log | Supported | Accepts JSON or form encoding, with content and source. metadata is accepted and ignored, and the response has an empty list. There's no token, because logs can't be deleted early. Times are unix seconds. |
GET /1/log/{id} | Supported | Same fields as the upload response. Add ?raw=1 to get the text in content. parsed and insights are accepted and ignored. |
GET /1/raw/{id} | Supported | The whole log as plain text. |
GET /1/limits | Supported | storageTime in seconds, maxLength in bytes and maxLines, with our numbers. |
GET /1/filters | Different | Always an empty list. minelog doesn't ask clients to trim or rewrite logs. It only hides private details on its own side. |
DELETE /1/log/{id} | Not supported | Returns 501. Logs are never deleted early, they expire on their own. |
POST /1/bulk/log/delete | Not supported | Returns 501, for the same reason. |
GET /1/insights/{id} | Later | Returns 501 until minelog can read crash causes and mod lists. |
POST /1/analyse | Later | Returns 501, on the same schedule as insights. |
/1/limits returns our numbers in the units mclo.gs uses:
{
"storageTime": 3456000,
"maxLength": 15728640,
"maxLines": 30000
}Our limits differ from theirs. A tool that reads /1/limits before uploading picks ours up by itself. One that hard codes the mclo.gs numbers stays inside them, which always fits.
Stability
/v2 is a draft until the API launches. After that:
- We can add new fields to a response at any time. If your client ignores fields it doesn't know, nothing breaks.
- Changes that would break a working client go into a new version,
/v3./v2keeps working alongside it. - The
/1paths follow mclo.gs. If they add something we'll look at it, but we can't promise the same day. - Logs are deleted after 40 days, whichever protocol saved them.