onelink.ninja logo onelink.ninja

API overview

A plain JSON REST API that mirrors the web flow: create a list, get an edit credential back, optionally claim it later. No API key, no signup, no OAuth dance — you can create a list with one curl.

curl -X POST https://onelink.ninja/api/lists 
  -H 'content-type: application/json' 
  -d '{"title":"My list","links":[{"url":"https://example.com","label":"Example"}]}'
{
	"id": "Q20d9AsqNh",
	"publicUrl": "https://onelink.ninja/l/Q20d9AsqNh",
	"editUrl": "https://onelink.ninja/l/Q20d9AsqNh/edit?token=xK_2_OQe-STVdmd_jiZdUhBoJefLYAmarx",
	"editToken": "xK_2_OQe-STVdmd_jiZdUhBoJefLYAmarx",
	"markdownUrl": "https://onelink.ninja/l/Q20d9AsqNh.md"
}

Full endpoint list in the API reference.

Authentication

Reads are open. Writes need one of two things:

  • Authorization: Bearer {editToken} — the token returned when the list was created. Works until the list is claimed.
  • A session cookie owning the list — for a claimed list, from a signed-in browser.

Once a list is claimed its editToken is destroyed and stops working. That is the same rule the web edit page follows.

Hand the editToken back to whoever you made the list for, and tell them it is the only way back in.

Requests

Writes must send content-type: application/json. A form-encoded body is rejected rather than parsed, so a stray HTML form can never reach a write endpoint.

Errors

Every failure has the same shape. Branch on code — the message is prose and may be reworded.

{
	"error": {
		"code": "invalid_credentials",
		"message": "That editToken is not valid for this list."
	}
}
codestatusmeans
invalid_body400not JSON, wrong content-type, or failed validation
missing_credentials401a write with no token and no session
invalid_credentials403wrong token, or a session that does not own the list
not_found404no list with that id
rate_limited429too many creates from your address

On a validation failure, error.details carries the specific field problems.

Note that a wrong credential is a 403, not a 404. Whether a list exists is already public via GET, so pretending otherwise would only make your own bugs harder to find.

Rate limits

Creating a list is limited to 10 per minute per IP — generous for a person, dull for a script. A 429 means wait, not that the request was malformed; the message tells you for how long. Successful creates carry X-RateLimit-Remaining.

Nothing else is limited: the other endpoints need a credential that had to be handed out, or are reads.

CORS

Every endpoint sends Access-Control-Allow-Origin: * and answers preflight, so browser extensions and bookmarklets work from any origin.

There is deliberately no Access-Control-Allow-Credentials. Browsers refuse to send cookies to a wildcard origin, which means another site cannot use a visitor’s signed-in session to edit their lists. Cross-origin callers authenticate with a Bearer token instead — one they can only have if somebody gave it to them.