# xi.pe > A pastebin with a plain-text API. POST some text, get back a short URL on a > line by itself. No account, no API key, no JSON envelope to unwrap. Built for pipes and for agents: the response body to a create is the URL and nothing else, so it drops straight into a variable or a message to a user. This document is also what `GET https://xi.pe/` returns to anything that is not a browser, so you can read the whole interface with one request. ## Why you might use this You have text a human needs to see — a long diff, a stack trace, a generated config, a report — and pasting it inline would flood the conversation. Put it here and hand over the link. Fair warning to pass on to whoever you share the link with: **anyone with the URL can read it, and it disappears after 7 days.** Do not put secrets in it. ## Create ``` curl --data-binary @- https://xi.pe/ <<'EOF' your text here EOF ``` Response, in full: ``` https://xi.pe/Ab3d ``` That is the whole body — one line, trailing newline, no JSON. So this works: ``` URL=$(printf '%s' "$text" | curl -s --data-binary @- https://xi.pe/) ``` Use `--data-binary`, not `-d`: `-d` strips newlines and will mangle anything with formatting. Add `?long` for a 23-character code instead of the usual 4–5: ``` $ echo 'Something awesome with long key!' | curl --data-binary @- https://xi.pe/?long https://xi.pe/wZu6D7CiXJNrt9ZnYTYhyt3 ``` 131 bits instead of 22.8. Short codes are unguessable by accident; long ones are unguessable on purpose — reach for it when the URL is the only thing keeping the content private. Everything else is identical: same 7-day expiry, same delete token, same API. ## Text only, UTF-8 only Content **must** be valid UTF-8. There is no encoding parameter, no charset option, and no binary mode — invalid UTF-8 is rejected with `400` on every input path. Nothing is transcoded, truncated, or silently repaired: the request either stores the exact bytes you sent or it fails. This is the whole point of the service, not a limitation of it. Because only text can get in, a xi.pe link can never hand someone an executable, an archive, or a payload wearing a text extension. Reads are always served as `text/plain; charset=utf-8` with `X-Content-Type-Options: nosniff`. If you genuinely need to move binary data, encode it first — base64 is valid UTF-8 and stores fine — and tell the recipient, because they will have to decode it by hand. That friction is deliberate. Do not treat it as a workaround to reach for by default. ## Read ``` curl https://xi.pe/Ab3d ``` Returns the exact bytes that were stored. A browser visiting the same URL gets a viewer page instead, with syntax highlighting and a copy button — the choice is made from `User-Agent`. Append `?raw` to force plain text, `?html` to force the page. On the viewer page, highlighting is off unless the URL asks: ``` https://xi.pe/Ab3d no highlighting https://xi.pe/Ab3d?h highlighting, language auto-detected https://xi.pe/Ab3d?h=go highlighting, forced to Go ``` `?h` **resolves**: once detection runs, the page rewrites the URL to name the grammar that won, so a link you copy carries `?h=go` rather than a request to guess again. `?h` stays valid input and re-resolves the same way. **If you know the language, name it.** Roughly 190 are accepted — anything highlight.js ships a grammar for, fetched on demand, whether or not it appears in the page's picker. Common aliases work: `js`, `py`, `rs`, `sh`, `yml`, `md`, `rb`, `ts`, `cpp`, `golang`. Auto-detection (`?h` with no value) is deliberately restricted to 26 common languages, because a wider candidate pool is measurably *worse* — extra grammars outbid the right answer rather than adding coverage. It is reliable on ordinary source and wrong on anything with no language to detect, so logs, stack traces and prose come out coloured as whatever grammar scored highest. Naming the language avoids that entirely and is far cheaper on a large paste. This only affects display — `?raw` and the stored bytes are untouched either way. ## Check a link without downloading it ``` curl -I https://xi.pe/Ab3d ``` `200` means it is still there, `404` means it is gone. The response also carries `X-Paste-Created` and `X-Paste-Expires` as RFC 3339 timestamps, so one request tells you both that the link works and how long it has left. Use this instead of re-fetching a large paste to see whether it survived. ## Delete Creating a paste returns an `X-Delete-Token` response header. Keep it if you might want to remove the paste early: ``` curl -sD- -o /dev/null --data-binary @- https://xi.pe/ < file.txt # read the header curl -X DELETE https://xi.pe/Ab3d -H "X-Delete-Token: " ``` The token is specific to that one paste and is not stored anywhere on the server in a usable form, so it cannot be recovered later. Deleting is optional; everything expires on its own. ## If you would rather have JSON Send `Accept: application/json` (or add `?output=json`) to any endpoint and you get a structured response instead — including on errors, so you never have to parse a sentence to find out what went wrong. ``` $ curl -s -H 'Accept: application/json' --data-binary @- https://xi.pe/ <<< 'hi' {"url":"https://xi.pe/Ab3d","code":"Ab3d","delete_token":"kZ8...","size":3,"expires":"2026-08-23T00:12:58Z"} $ curl -s -H 'Accept: application/json' https://xi.pe/Ab3d {"url":"https://xi.pe/Ab3d","code":"Ab3d","data":"hi","size":3,"created":"...","expires":"..."} $ curl -s -H 'Accept: application/json' https://xi.pe/nope {"error":"Not found or expired","status":404} ``` This is the easiest way to capture the delete token, since it arrives in the body rather than in a header. It is strictly opt-in: `Accept: */*`, which is what `curl` sends by default, still gets the bare URL. ## From a browser Every endpoint sends `Access-Control-Allow-Origin: *` and answers preflight, so page-side JavaScript can create, read and delete without a proxy. `X-Delete-Token`, `X-Paste-Created` and `X-Paste-Expires` are listed in `Access-Control-Expose-Headers`, so `fetch()` can actually read them. Credentials are deliberately not allowed cross-origin: a delete from another origin has to present the token explicitly, and cannot ride on a cookie the visitor happens to hold. ## Limits and behaviour - **Lifetime:** 7 days, then it is gone. There are no permanent links. - **Size:** 2 MB. Larger requests are rejected with `413` — nothing is silently truncated. - **Codes:** 4–5 characters, randomly generated, case-sensitive. `?long` gives 23 characters instead. - **Cost:** free, and there is no sign-up step to automate around. Errors are one short line of plain text saying exactly what is wrong, e.g. `Error 400: Content must be valid UTF-8`. The one deliberate exception is `404`, which is always `Not found or expired` regardless of the reason — a message that varied would tell you which codes exist. Status codes: `200` created or found · `303` form redirect · `400` bad input · `401` wrong or missing delete token · `404` unknown or expired code · `413` too large · `500` server or storage error · `503` temporary, and always with `Retry-After` — wait that many seconds rather than inventing a backoff. ## Please don't Be a considerate client. This is a small service run by one person. - Don't use it as a database, a CDN, or a backup target. - Don't poll it, and don't enumerate codes — they are random, and guessing at them is the one thing that will get traffic blocked. - Don't post other people's private data, credentials, or anything you would not want served publicly for a week. Abuse: abuse@xi.pe ## More - Privacy and terms: https://xi.pe/privacy - Source (MIT): https://github.com/drewstreib/xipe-go - Operated by alt.org