{
  "openapi": "3.1.0",
  "info": {
    "title": "onelink.ninja API",
    "version": "1.0.0",
    "summary": "Create, read and edit shareable link lists. No API key needed.",
    "description": "Creating a list needs no credentials and returns an `editToken`. It is the only credential for an unclaimed list — hand it to the person you made the list for. Plain-text guide: https://onelink.ninja/llms.txt. MCP server: https://onelink.ninja/mcp."
  },
  "servers": [
    {
      "url": "https://onelink.ninja"
    }
  ],
  "externalDocs": {
    "url": "https://onelink.ninja/documentation/api-overview"
  },
  "paths": {
    "/api/lists": {
      "post": {
        "operationId": "createList",
        "summary": "Create a list. No authentication needed; returns the edit token.",
        "description": "Rate limited to 10 creates per 60 seconds per IP address.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ListCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string"
                },
                "description": "The API URL of the list."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Creates left in the current window."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateResponse"
                }
              }
            }
          },
          "400": {
            "description": "Not JSON, or failed validation; see `details`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "More than 10 creates in 60 seconds from this IP. Wait, then retry.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/lists/random": {
      "get": {
        "operationId": "getRandomList",
        "summary": "Get a random public list that has at least one link.",
        "security": [],
        "responses": {
          "200": {
            "description": "A random list.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "id",
                    "publicUrl",
                    "markdownUrl"
                  ],
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "publicUrl": {
                      "type": "string",
                      "format": "uri"
                    },
                    "markdownUrl": {
                      "type": "string",
                      "format": "uri"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No list has links yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/lists/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "The list id from `publicUrl` (`/l/{id}`).",
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "operationId": "getList",
        "summary": "Read a public list.",
        "security": [],
        "responses": {
          "200": {
            "description": "The list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/List"
                }
              }
            }
          },
          "404": {
            "description": "No list with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateList",
        "summary": "Update a list. `links`, if sent, replaces the whole array.",
        "description": "Omitted fields are left alone. Sending `links` replaces every link.",
        "security": [
          {
            "editToken": []
          },
          {
            "session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ListUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/List"
                }
              }
            }
          },
          "400": {
            "description": "Not JSON, or failed validation; see `details`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No editToken and no session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Wrong token, or the list was claimed and the token retired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No list with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteList",
        "summary": "Delete a list.",
        "security": [
          {
            "editToken": []
          },
          {
            "session": []
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "deleted",
                    "id"
                  ],
                  "properties": {
                    "deleted": {
                      "const": true
                    },
                    "id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No editToken and no session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Wrong token, or the list was claimed and the token retired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No list with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/lists/{id}/links": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "The list id from `publicUrl` (`/l/{id}`).",
          "schema": {
            "type": "string"
          }
        }
      ],
      "post": {
        "operationId": "addLink",
        "summary": "Append one link to the end of a list.",
        "description": "Fails with `invalid_body` once the list holds 20 links.",
        "security": [
          {
            "editToken": []
          },
          {
            "session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Link"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The list with the new link at the end.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/List"
                }
              }
            }
          },
          "400": {
            "description": "Not JSON, failed validation, or the list is full.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No editToken and no session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Wrong token, or the list was claimed and the token retired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No list with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "editToken": {
        "type": "http",
        "scheme": "bearer",
        "description": "The `editToken` returned by createList. Stops working once the list is claimed by an account."
      },
      "session": {
        "type": "apiKey",
        "in": "cookie",
        "name": "__Secure-better-auth.session_token",
        "description": "For claimed lists: the session cookie of the owning account, from a signed-in browser on this site. Not usable cross-origin."
      }
    },
    "schemas": {
      "ListCreate": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "description": {
            "type": "string",
            "maxLength": 1000
          },
          "links": {
            "default": [],
            "maxItems": 20,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "url": {
                  "type": "string",
                  "format": "uri",
                  "pattern": "^(http|https|mailto|tel):",
                  "description": "An absolute URL. Schemes: http, https, mailto, tel."
                },
                "label": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 200
                },
                "description": {
                  "type": "string",
                  "maxLength": 1000
                }
              },
              "required": [
                "url",
                "label"
              ]
            }
          }
        },
        "required": [
          "title"
        ]
      },
      "ListUpdate": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "description": {
            "type": "string",
            "maxLength": 1000
          },
          "links": {
            "maxItems": 20,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "url": {
                  "type": "string",
                  "format": "uri",
                  "pattern": "^(http|https|mailto|tel):",
                  "description": "An absolute URL. Schemes: http, https, mailto, tel."
                },
                "label": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 200
                },
                "description": {
                  "type": "string",
                  "maxLength": 1000
                }
              },
              "required": [
                "url",
                "label"
              ]
            }
          }
        }
      },
      "Link": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "pattern": "^(http|https|mailto|tel):",
            "description": "An absolute URL. Schemes: http, https, mailto, tel."
          },
          "label": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "description": {
            "type": "string",
            "maxLength": 1000
          }
        },
        "required": [
          "url",
          "label"
        ]
      },
      "List": {
        "type": "object",
        "required": [
          "id",
          "title",
          "url",
          "markdownUrl",
          "claimed",
          "links"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The public page."
          },
          "markdownUrl": {
            "type": "string",
            "format": "uri"
          },
          "claimed": {
            "type": "boolean",
            "description": "Claimed lists belong to an account; their editToken no longer works."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "links": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "url",
                "label"
              ],
              "properties": {
                "url": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                },
                "description": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          }
        }
      },
      "CreateResponse": {
        "type": "object",
        "required": [
          "id",
          "publicUrl",
          "editUrl",
          "editToken",
          "markdownUrl"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "publicUrl": {
            "type": "string",
            "format": "uri",
            "description": "Share this."
          },
          "editUrl": {
            "type": "string",
            "format": "uri",
            "description": "Secret edit page for the human. Contains the editToken."
          },
          "editToken": {
            "type": "string",
            "description": "The only credential for this list. Give it to the user and tell them so."
          },
          "markdownUrl": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "invalid_body",
                  "missing_credentials",
                  "invalid_credentials",
                  "not_found",
                  "method_not_allowed",
                  "rate_limited"
                ]
              },
              "message": {
                "type": "string",
                "description": "Prose; may change. Branch on `code`."
              },
              "details": {
                "description": "For `invalid_body`: the failing fields, each with a `path`."
              }
            }
          }
        }
      }
    }
  }
}