{"openapi":"3.1.0","info":{"title":"fskk API","version":"1.0.0","description":"Publish HTML as a guest with rate limits, or create and manage persistent pages with an API key from Settings → API Keys."},"servers":[{"url":"https://fskk.site/api/v1"}],"tags":[{"name":"pages","description":"Link-in-bio pages"},{"name":"uploads","description":"Chunked HTML uploads for guest or signed-in publishing"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key issued in Settings → API Keys. Send as `Authorization: Bearer fsk_…`."}},"parameters":{"pageParam":{"name":"page","in":"query","description":"Page number","schema":{"type":"integer","minimum":1,"default":1}},"perPageParam":{"name":"per_page","in":"query","description":"Items per page (1–100)","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},"searchParam":{"name":"q","in":"query","description":"Search term matched against nanoid, custom_uri, title and subtitle.","schema":{"type":"string"}},"pageIdPath":{"name":"id","in":"path","required":true,"description":"Page nanoid or custom URI.","schema":{"type":"string"}}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]},"Page":{"type":"object","properties":{"id":{"type":"string","description":"Public nanoid used to address the page."},"url":{"type":"string","description":"Shareable URL of the page."},"custom_uri":{"type":["string","null"],"description":"Optional friendly alias."},"theme":{"type":"string","enum":["default_theme","purple_theme","green_theme","raw_theme"]},"private":{"type":"boolean"},"show_in_feed":{"type":"boolean"},"html_data":{"type":"object","description":"Page content. Shape depends on theme: template themes use `title`, `subtitle`, `links`, `background_color`, `text_color`; `raw_theme` uses `html` and `css`."},"is_supporter_page":{"type":"boolean"},"expiration_date":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","url","theme","private","html_data","is_supporter_page","created_at","updated_at"]},"PageList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Page"}},"meta":{"type":"object","properties":{"page":{"type":"integer"},"perPage":{"type":"integer"},"totalItems":{"type":"integer"},"totalPages":{"type":"integer"}},"required":["page","perPage","totalItems","totalPages"]}},"required":["data","meta"]},"PageCreate":{"type":"object","properties":{"theme":{"type":"string","enum":["default_theme","purple_theme","green_theme","raw_theme"],"description":"Page theme. Defaults to `default_theme`."},"private":{"type":"boolean","default":false},"show_in_feed":{"type":"boolean","default":false,"description":"Opt in to showing this public page in the community feed."},"custom_uri":{"type":"string","description":"Optional friendly alias. Letters, numbers, hyphens, underscores only.","pattern":"^[a-zA-Z0-9_-]+$"},"html_data":{"type":"object","description":"Page content. If omitted, sensible defaults for the chosen theme are used. Template themes expect `title`, `subtitle`, `links`, `background_color`, `text_color`; `raw_theme` expects `html` and `css`."}}},"PageUpdate":{"type":"object","properties":{"theme":{"type":"string","enum":["default_theme","purple_theme","green_theme","raw_theme"]},"private":{"type":"boolean"},"show_in_feed":{"type":"boolean","description":"Opt in to the public page feed. Private pages are never listed."},"custom_uri":{"type":["string","null"],"description":"Pass null to remove the custom URI.","pattern":"^[a-zA-Z0-9_-]+$"},"html_data":{"type":"object","description":"Merged over the current content."}}},"PageHtml":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"html":{"type":"string","description":"Full HTML document string for the page."}},"required":["id","html"]}},"required":["data"]},"UploadInit":{"type":"object","properties":{"fileName":{"type":"string","pattern":"\\.html?$"},"totalBytes":{"type":"integer","minimum":1,"maximum":10485760},"purpose":{"type":"string","const":"page"},"visibility":{"type":"string","enum":["public","private"]},"showInFeed":{"type":"boolean","default":false}},"required":["fileName","totalBytes","purpose","visibility"]},"UploadSession":{"type":"object","properties":{"uploadId":{"type":"string","format":"uuid"},"token":{"type":"string","description":"Secret upload token. Keep it private."},"chunkSize":{"type":"integer","const":2097152},"chunkCount":{"type":"integer","minimum":1,"maximum":5}},"required":["uploadId","token","chunkSize","chunkCount"]},"UploadChunk":{"type":"object","properties":{"token":{"type":"string"},"chunkIndex":{"type":"integer","minimum":0,"maximum":4},"content":{"type":"string","contentEncoding":"base64","description":"One chunk of up to 2 MiB."}},"required":["token","chunkIndex","content"]},"UploadComplete":{"type":"object","properties":{"token":{"type":"string"}},"required":["token"]},"UploadResult":{"type":"object","properties":{"url":{"type":"string","format":"uri-reference"}},"required":["url"]}}},"paths":{"/pages":{"get":{"tags":["pages"],"summary":"List pages","description":"Returns the pages owned by the API key owner, most recently updated first.","operationId":"listPages","security":[{"bearerAuth":["pages:read"]}],"parameters":[{"$ref":"#/components/parameters/pageParam"},{"$ref":"#/components/parameters/perPageParam"},{"$ref":"#/components/parameters/searchParam"}],"responses":{"200":{"description":"Pages","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageList"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Key lacks the pages:read scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["pages"],"summary":"Create a page","description":"Creates a new page for the API key owner, respecting their page limit.","operationId":"createPage","security":[{"bearerAuth":["pages:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageCreate"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page"}}}},"400":{"description":"Invalid body (e.g. bad custom_uri)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing scope or page limit reached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"custom_uri already taken","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/pages/{id}":{"get":{"tags":["pages"],"summary":"Get a page","description":"Fetches a single page by nanoid or custom URI.","operationId":"getPage","security":[{"bearerAuth":["pages:read"]}],"parameters":[{"$ref":"#/components/parameters/pageIdPath"}],"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Page not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["pages"],"summary":"Update a page","description":"Updates a page. Fields are merged; `html_data` is merged over the current content and reconciled per theme.","operationId":"updatePage","security":[{"bearerAuth":["pages:write"]}],"parameters":[{"$ref":"#/components/parameters/pageIdPath"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageUpdate"}}}},"responses":{"200":{"description":"Updated page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Page"}}}},"400":{"description":"Invalid body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Page not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"custom_uri already taken","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["pages"],"summary":"Delete a page","description":"Permanently deletes a page by nanoid or custom URI.","operationId":"deletePage","security":[{"bearerAuth":["pages:write"]}],"parameters":[{"$ref":"#/components/parameters/pageIdPath"}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"success":{"type":"boolean"},"id":{"type":"string"}},"required":["success","id"]}},"required":["data"]}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Page not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/pages/{id}/html":{"get":{"tags":["pages"],"summary":"Get rendered HTML","description":"Returns the page rendered as a complete HTML document (templates are converted to inline-styled HTML; raw pages pass through your custom HTML/CSS). Use it to embed a page in an iframe.","operationId":"getPageHtml","security":[{"bearerAuth":["pages:read"]}],"parameters":[{"$ref":"#/components/parameters/pageIdPath"}],"responses":{"200":{"description":"Rendered HTML","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageHtml"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Page not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/uploads/init":{"post":{"tags":["uploads"],"summary":"Start an HTML upload","description":"Creates a 30-minute upload session for a single HTML file up to 10 MiB. No API key is needed for public guest pages (10 uploads/hour/IP; URLs expire after 24 hours). Send a pages:write bearer key to create a persistent page owned by your account and use the higher API rate limit. Chunk size is 2 MiB.","operationId":"startHtmlUpload","servers":[{"url":"/api"}],"security":[{"bearerAuth":["pages:write"]},{}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadInit"}}}},"responses":{"200":{"description":"Upload session created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadSession"}}}},"400":{"description":"Invalid file name, size, purpose, or visibility"},"401":{"description":"Invalid API key when Authorization was supplied"},"429":{"description":"Guest or API rate limit reached; see Retry-After"}}}},"/uploads/{uploadId}/chunk":{"post":{"tags":["uploads"],"summary":"Upload one HTML chunk","description":"Uploads a zero-based, standard-base64 chunk. Use the upload token from the init response. Send the same pages:write bearer key when the session belongs to an API-key account.","operationId":"uploadHtmlChunk","servers":[{"url":"/api"}],"security":[{"bearerAuth":["pages:write"]},{}],"parameters":[{"name":"uploadId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadChunk"}}}},"responses":{"200":{"description":"Chunk stored"},"400":{"description":"Invalid base64 or chunk index"},"403":{"description":"Invalid token or not the upload owner"},"404":{"description":"Upload session expired or not found"}}}},"/uploads/{uploadId}/complete":{"post":{"tags":["uploads"],"summary":"Complete an HTML upload","description":"Verifies all chunks and publishes the page. Guest pages are public and expire after 24 hours; authenticated pages are added to the API key owner account.","operationId":"completeHtmlUpload","servers":[{"url":"/api"}],"security":[{"bearerAuth":["pages:write"]},{}],"parameters":[{"name":"uploadId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadComplete"}}}},"responses":{"200":{"description":"Published page URL","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UploadResult"}}}},"400":{"description":"Invalid HTML or account page limit reached"},"403":{"description":"Invalid token or not the upload owner"},"409":{"description":"One or more chunks are missing"},"413":{"description":"Stored HTML is too large"}}}}}}