{"openapi":"3.1.0","info":{"title":"ListDrop public API","version":"1.0.0","summary":"Create lists for a user to keep, and read any public list as markdown.","description":"ListDrop is a social app for living lists (you follow lists, not people). This spec covers the keyless, anonymous surface built for AI agents: create a private draft list and hand the user a claim URL (they keep it with one click), and read public lists and topic hubs as markdown. Auth is optional: everything works keyless (the claim-URL flow keeps credentials out of agents' hands), and agents that should act on a user's own account can connect via OAuth 2.1 — authorization-code + PKCE, dynamic client registration (RFC 7591), metadata at /.well-known/oauth-authorization-server (RFC 8414) and /.well-known/oauth-protected-resource (RFC 9728). The same capabilities are exposed as an MCP server (see x-mcp). Versioning: the path carries the major version (/api/v1/…); breaking changes ship as /api/v2 and v1 keeps working for at least 90 days, announced with Deprecation and Sunset headers on v1 responses (see x-versioning). Every v1 response carries API-Version plus RateLimit / RateLimit-Policy headers (Retry-After on 429). Human docs: https://www.listdrop.co/ai","termsOfService":"https://www.listdrop.co/terms","contact":{"name":"ListDrop","email":"hello@listdrop.co","url":"https://www.listdrop.co/contact"}},"servers":[{"url":"https://www.listdrop.co"}],"externalDocs":{"description":"Agent guide (human-readable)","url":"https://www.listdrop.co/ai"},"x-llms-txt":"https://www.listdrop.co/llms.txt","security":[],"x-mcp":{"transport":"streamable-http","url":"https://www.listdrop.co/mcp","auth":"optional OAuth 2.1; keyless works","tools":["create_list","get_list","my_lists","send_feedback"],"note":"GET /mcp without an event-stream Accept header returns a JSON server card."},"x-versioning":{"scheme":"URL path major version (/api/v1). Unversioned /api/agent/lists is an alias of v1.","responseHeader":"API-Version: 1 on every v1 response.","deprecationPolicy":"Breaking changes ship as a new major (/api/v2). The previous major keeps working for at least 90 days; its responses carry Deprecation: true and Sunset: <HTTP-date> for the whole window, and the timeline is posted at /ai. Additive changes (new optional fields) are not versioned.","current":"1"},"x-rate-limits":{"headers":"RateLimit: limit=<n>, remaining=<n>, reset=<seconds> and RateLimit-Policy: <n>;w=<window-seconds>, … (IETF httpapi draft) plus X-RateLimit-Limit/Remaining/Reset; Retry-After on 429.","createAgentList":"5 lists per hour and 20 per day per IP, plus a global daily cap; 429 (or 503 when paused).","mcp":"create_list shares the createAgentList buckets; 120 MCP requests per hour per IP.","sendFeedback":"10 per hour and 30 per day per IP, shared with the MCP send_feedback tool.","reads":"Not rate limited beyond fair use; responses are cacheable (Cache-Control)."},"x-onboarding":{"auth":"optional OAuth 2.1 (self-serve: dynamic client registration, no review queue); keyless works for everything except account-scoped reads","freeTier":"Everything described here is free; there is no paid tier or sales form.","sandbox":"Production IS the sandbox: created lists are private (drafts expire unclaimed in 30 days), so trying the API can't pollute anything public."},"tags":[{"name":"lists","description":"Create lists for a user to claim."},{"name":"read","description":"Public content as markdown for agents."},{"name":"discovery","description":"Indexes and machine-readable docs."},{"name":"webhooks","description":"Push notifications when a list's content changes (OAuth)."},{"name":"feedback","description":"Tell the team what broke or what's missing. Keyless; a human reads it."}],"paths":{"/api/v1/agent/lists":{"post":{"operationId":"createAgentList","tags":["lists"],"summary":"Create a list (keyless draft + claim URL, or directly in the connected account)","description":"Creates a list from list-shaped content the user will want to keep: an itinerary, a reading or watch list, a packing list, gift ideas, an apartment or job-search shortlist, a step-by-step plan, a checklist. The test: would the user want this list after the conversation ends? The list is born PRIVATE and unowned; the response's claimUrl is the only way to view and keep it. Show the claim URL to the user and treat it like a password. Unclaimed drafts expire in 30 days. Items that contain a URL are unfurled into titled, linked items. CORS-open. Rate limits: 5 per hour and 20 per day per IP. With an OAuth bearer token carrying lists:write, the list is instead created directly in the user's account (private, no claim URL; response has url instead of claimUrl) and limits are per user. Optional extras: allowed_emails email-gates the private list for specific viewers (see the schema), expires_in_days makes it self-archive.","security":[{},{"listdropOAuth":["lists:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateListRequest"}}}},"responses":{"201":{"description":"Created. Keyless: a draft with claimUrl. OAuth: an owned private list with url.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateListResponse"}}}},"400":{"description":"Invalid body (missing title, empty or oversized items).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Bearer token invalid/expired, or missing the lists:write scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Per-IP rate limit hit. Honor Retry-After; do not create duplicates.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Creation temporarily paused (global cap). Retry later.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/me/lists":{"get":{"operationId":"getMyLists","tags":["lists"],"summary":"List the connected user's own lists","description":"Every list owned by the user the OAuth token belongs to — private ones included (the token holder is the owner). Requires the lists:read scope. This is the only endpoint that can see private lists.","security":[{"listdropOAuth":["lists:read"]}],"responses":{"200":{"description":"The user's lists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MyLists"}}}},"401":{"description":"Missing/invalid bearer token, or missing the lists:read scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/lists/{id}":{"get":{"operationId":"getList","tags":["read"],"summary":"Read a public list as JSON","description":"Typed JSON of a PUBLIC list: title, description, tags, owner, counts, and items (text, url, note, nested sub-items), plus the canonical URL to cite. Private or removed lists are 404, never leaked. Markdown twin: /l/{id}/md.","parameters":[{"name":"id","in":"path","required":true,"description":"The list id, the segment after /l/ in any ListDrop list URL.","schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{6,20}$"}}],"responses":{"200":{"description":"The list.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicList"}}}},"404":{"description":"Not found or not public.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tags/{tag}":{"get":{"operationId":"getTag","tags":["read"],"summary":"Read a topic hub as JSON","description":"Every public list carrying a tag, quality-ranked (followers, then recency), with each list's page and API URLs. Use it to find human-curated lists on a topic before reading one with getList. Markdown twin: /t/{tag}/md.","parameters":[{"name":"tag","in":"path","required":true,"description":"Tag slug, lowercase (e.g. books, tacos-cdmx).","schema":{"type":"string","minLength":1,"maxLength":40}}],"responses":{"200":{"description":"The hub.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagHub"}}}},"404":{"description":"No public lists carry this tag.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/l/{id}/md":{"get":{"operationId":"getListMarkdown","tags":["read"],"summary":"Read a public list as markdown","description":"Clean markdown rendering of a PUBLIC list: title, description, tags, items (with links and notes), and the canonical URL to cite. Private or removed lists are 404, never leaked. Equivalent: request the list page itself with `Accept: text/markdown`.","parameters":[{"name":"id","in":"path","required":true,"description":"The list id, the segment after /l/ in any ListDrop list URL.","schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{6,20}$"}}],"responses":{"200":{"description":"The list as markdown.","headers":{"Vary":{"schema":{"type":"string"},"description":"Accept"}},"content":{"text/markdown":{"schema":{"type":"string"}}}},"404":{"description":"Not found or not public.","content":{"text/markdown":{"schema":{"type":"string"}}}}}}},"/t/{tag}/md":{"get":{"operationId":"getTagMarkdown","tags":["read"],"summary":"Read a topic hub as markdown","description":"Every public list carrying a tag, quality-ranked, with a link to each list's page and /md view. The traversal layer: llms.txt → tag → lists. Equivalent: `Accept: text/markdown` on /t/{tag}.","parameters":[{"name":"tag","in":"path","required":true,"description":"Tag slug, lowercase (e.g. books, tacos-cdmx).","schema":{"type":"string","minLength":1,"maxLength":40}}],"responses":{"200":{"description":"The tag hub as markdown.","headers":{"Vary":{"schema":{"type":"string"},"description":"Accept"}},"content":{"text/markdown":{"schema":{"type":"string"}}}},"404":{"description":"No public lists carry this tag.","content":{"text/markdown":{"schema":{"type":"string"}}}}}}},"/api/v1/lists/{id}/items":{"patch":{"operationId":"bulkEditItems","tags":["lists"],"summary":"Bulk add, update, remove, and reorder items","description":"Structured item operations in one call on a list the connected account can edit (owner or collaborator). `ops` is an ordered array of {op:'add', text, note?} / {op:'update', id, text?, note?} / {op:'remove', id}; the optional `order` array of root item ids reorders the list (listed ids first, the rest keep their relative order). Root items only; adds run through the magic-paste pipeline (a URL in the text unfurls into a titled, linked item). Followers see one activity entry per verb, not one per item. Caps: 100 ops per call, 60 calls/hour.","security":[{"listdropOAuth":["lists:write"]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The list id, the segment after /l/ in any ListDrop list URL.","schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{6,20}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkItemsRequest"}}}},"responses":{"200":{"description":"Applied. Lists the created item ids and the update/remove counts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkItemsResponse"}}}},"400":{"description":"Malformed ops (unknown op, empty text, item not on this list).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing/invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"List not found, or the account can't edit it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit hit. Honor Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/lists/{id}/allowed-emails":{"get":{"operationId":"getAllowedEmails","tags":["lists"],"summary":"Read a list's email guest list","description":"The email addresses allowed to view this private list (see createAgentList's allowed_emails). Owner only: requires an OAuth token belonging to the list's owner.","security":[{"listdropOAuth":["lists:read"]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The list id, the segment after /l/ in any ListDrop list URL.","schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{6,20}$"}}],"responses":{"200":{"description":"The guest list (empty array = not gated).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AllowedEmails"}}}},"401":{"description":"Missing/invalid bearer token, or missing the lists:read scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"List not found, or the account isn't its owner.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"operationId":"setAllowedEmails","tags":["lists"],"summary":"Replace a list's email guest list","description":"Replaces the set of addresses allowed to VIEW this private list (view-only; never edit rights). An empty array removes the gate — the list goes back to plain private. Owner only. Guests verify an allowlisted address at the list's URL via a mailed one-time link; no account needed. Cap: 20 addresses.","security":[{"listdropOAuth":["lists:write"]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The list id, the segment after /l/ in any ListDrop list URL.","schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{6,20}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["emails"],"properties":{"emails":{"type":"array","maxItems":20,"items":{"type":"string","format":"email"}}}}}}},"responses":{"200":{"description":"The new guest list.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AllowedEmails"}}}},"400":{"description":"Invalid or too many addresses.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing/invalid bearer token, or missing the lists:write scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"List not found, or the account isn't its owner.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/lists/{id}/webhooks":{"post":{"operationId":"createWebhook","tags":["webhooks"],"summary":"Subscribe a webhook to a list's changes","description":"Registers an https endpoint that receives a JSON POST whenever the list's content changes (events: added, removed, edited, sorted, renamed, deleted). The list must be visible to the connected account. Deliveries carry X-ListDrop-Event and X-ListDrop-Signature: sha256=<hex HMAC-SHA256 of the raw body> using the per-hook secret returned ONCE here. Ten consecutive failures disable the hook. Caps: 3 hooks per list, 20 per account.","security":[{"listdropOAuth":["lists:read"]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The list id, the segment after /l/ in any ListDrop list URL.","schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{6,20}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","description":"https endpoint to POST deliveries to."}}}}}},"responses":{"201":{"description":"Registered. The secret appears only in this response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookCreated"}}}},"400":{"description":"Bad URL (must be public https) or webhook cap reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing/invalid bearer token, or missing the lists:read scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"List not found or not visible to this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit hit. Honor Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/webhooks":{"get":{"operationId":"listWebhooks","tags":["webhooks"],"summary":"List the connected account's webhooks","description":"Every webhook this account registered, across all lists, with delivery health (failures, last status, disabledAt). Secrets are never shown again — delete and re-create to rotate one.","security":[{"listdropOAuth":["lists:read"]}],"responses":{"200":{"description":"The account's webhooks.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookList"}}}},"401":{"description":"Missing/invalid bearer token, or missing the lists:read scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/webhooks/{id}":{"delete":{"operationId":"deleteWebhook","tags":["webhooks"],"summary":"Delete a webhook","description":"Removes one of the connected account's webhooks permanently. Only the account that registered a webhook can delete it; there is no way to see or affect anyone else's.","security":[{"listdropOAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"The webhook id from createWebhook or listWebhooks.","schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"type":"object","required":["ok"],"properties":{"ok":{"type":"boolean"}}}}}},"401":{"description":"Missing/invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No webhook with that id on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/feedback":{"post":{"operationId":"sendFeedback","tags":["feedback"],"summary":"Tell the ListDrop team what broke or what's missing","description":"Send feedback to the people who build ListDrop: a bug, a capability you needed and couldn't find, a field this API is missing, wrong documentation, or what your user said about a list you saved for them. No account and no key, CORS-open, and a human reads every message. This is the supported way to ask for an API change; there is no sales form and no support queue behind a login. Send the substance, never secrets: claim URLs, tokens, and passwords are stripped on arrival, and personal data doesn't belong here. Include an email only if your user asked for a reply and gave you theirs. Rate limits: 10 per hour and 30 per day per IP (shared with the MCP send_feedback tool).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackRequest"}}}},"responses":{"201":{"description":"Received. A person reads it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackResponse"}}}},"400":{"description":"Empty or unusable message body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Per-IP rate limit hit. The earlier messages arrived; don't retry them.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Feedback temporarily paused (global cap). Email hello@listdrop.co instead.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"operationId":"describeFeedback","tags":["feedback"],"summary":"Describe the feedback endpoint","description":"Returns the accepted body, the kinds, the rate limits, and where humans send feedback. Here because agents and scanners probe endpoints with GET before writing to them.","responses":{"200":{"description":"Endpoint description.","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/openapi.json":{"get":{"operationId":"getOpenApi","tags":["discovery"],"summary":"This specification","description":"OpenAPI 3.1 JSON for the public surface. CORS-open.","responses":{"200":{"description":"The spec.","content":{"application/json":{"schema":{"type":"object"}}}}}}}},"components":{"securitySchemes":{"listdropOAuth":{"type":"oauth2","description":"OAuth 2.1: authorization-code + PKCE (S256), public clients via RFC 7591 dynamic registration at https://www.listdrop.co/api/oauth/register. Metadata: https://www.listdrop.co/.well-known/oauth-authorization-server","flows":{"authorizationCode":{"authorizationUrl":"https://www.listdrop.co/oauth/authorize","tokenUrl":"https://www.listdrop.co/api/oauth/token","refreshUrl":"https://www.listdrop.co/api/oauth/token","scopes":{"lists:read":"Read the user's own lists, including private ones","lists:write":"Create lists in the user's account (created private)"}}}}},"schemas":{"Error":{"type":"object","description":"Every error is JSON with a stable machine code and a human message.","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable message."},"code":{"type":"string","description":"Stable machine code.","enum":["invalid_request","rate_limited","paused","not_found","method_not_allowed"]},"hint":{"type":"string","description":"What to do next (where it helps)."},"docs":{"type":"string","format":"uri","description":"Link to the relevant docs."}}},"ListItemInput":{"oneOf":[{"type":"string","maxLength":200,"description":"Item text; a URL inside is unfurled."},{"type":"object","required":["text"],"properties":{"text":{"type":"string","minLength":1,"maxLength":200},"note":{"type":"string","maxLength":140,"description":"Short note shown under the item."}}}]},"CreateListRequest":{"type":"object","required":["title","items"],"properties":{"title":{"type":"string","minLength":1,"maxLength":80},"emoji":{"type":"string","maxLength":8,"description":"Optional single emoji for the list."},"description":{"type":"string","maxLength":280},"tags":{"type":"array","maxItems":8,"items":{"type":"string"},"description":"Topic tags, plain words (books, travel)."},"items":{"type":"array","minItems":1,"maxItems":50,"items":{"$ref":"#/components/schemas/ListItemInput"}},"allowed_emails":{"type":"array","maxItems":20,"items":{"type":"string","format":"email"},"description":"Optional email gate: these addresses can VIEW the private list at its URL after verifying the address via a mailed one-time link — no ListDrop account needed. View-only; omit for the classic unlisted claim-link behavior."},"expires_in_days":{"type":"integer","minimum":1,"maximum":365,"description":"Optional self-archive: after this many days the list becomes visible to its owner only (public lists unpublish, email gates close)."}}},"BulkItemsRequest":{"type":"object","properties":{"ops":{"type":"array","maxItems":100,"description":"Ordered operations. add: {op, text, note?}; update: {op, id, text?, note?}; remove: {op, id}.","items":{"type":"object","required":["op"],"properties":{"op":{"type":"string","enum":["add","update","remove"]},"id":{"type":"string","description":"Target item id (update/remove)."},"text":{"type":"string","maxLength":200},"note":{"type":["string","null"],"maxLength":140,"description":"null/empty clears the note."}}}},"order":{"type":"array","items":{"type":"string"},"description":"Root item ids in the desired order (listed first; the rest keep their relative order)."}}},"BulkItemsResponse":{"type":"object","required":["ok","added","updatedCount","removedCount","reordered"],"properties":{"ok":{"type":"boolean"},"added":{"type":"array","description":"Created items, in op order (text is the post-unfurl title).","items":{"type":"object","required":["id","text"],"properties":{"id":{"type":"string"},"text":{"type":"string"}}}},"updatedCount":{"type":"integer"},"removedCount":{"type":"integer"},"reordered":{"type":"boolean"}}},"FeedbackRequest":{"type":"object","required":["message"],"description":"One message. Everything except `message` is optional.","properties":{"message":{"type":"string","minLength":4,"maxLength":2000,"description":"What happened, in plain words. Concrete beats polite: the call you made, what you expected, what you got."},"kind":{"type":"string","enum":["problem","idea","praise","other"],"description":"Optional hint for triage; defaults to other."},"source":{"type":"string","enum":["agent","mcp","api","cli","web"],"description":"Which kind of client you are. Self-reported, used only for grouping."},"email":{"type":"string","format":"email","description":"Only if a reply is wanted, and only an address its owner gave you."},"path":{"type":"string","description":"Same-origin path the feedback is about, e.g. /l/abc123 or /api/v1/agent/lists."},"context":{"type":"object","description":"Flat map of extra detail (client name, endpoint, error code, model). Values are strings; at most 12 keys.","additionalProperties":{"type":"string"}}}},"FeedbackResponse":{"type":"object","required":["ok","id","message"],"properties":{"ok":{"type":"boolean"},"id":{"type":"string","description":"Reference for this message."},"message":{"type":"string","description":"Confirmation you can relay to your user."}}},"AllowedEmails":{"type":"object","required":["listId","emails"],"properties":{"listId":{"type":"string"},"emails":{"type":"array","items":{"type":"string","format":"email"},"description":"The current guest list; empty = the list is not email-gated."}}},"WebhookCreated":{"type":"object","required":["id","listId","url","secret","message"],"properties":{"id":{"type":"string"},"listId":{"type":"string"},"url":{"type":"string","format":"uri"},"secret":{"type":"string","description":"HMAC-SHA256 signing secret (whsec_…). Shown exactly once — store it now."},"message":{"type":"string"}}},"WebhookList":{"type":"object","required":["webhooks"],"properties":{"webhooks":{"type":"array","items":{"type":"object","required":["id","listId","url","createdAt","failures","disabledAt","lastStatus","lastDeliveryAt"],"properties":{"id":{"type":"string"},"listId":{"type":"string"},"url":{"type":"string","format":"uri"},"createdAt":{"type":"string","format":"date-time"},"failures":{"type":"integer","description":"Consecutive delivery failures (10 disables the hook)."},"disabledAt":{"type":["string","null"],"format":"date-time"},"lastStatus":{"type":["string","null"],"description":"HTTP status or error code of the last delivery."},"lastDeliveryAt":{"type":["string","null"],"format":"date-time"}}}}}},"PublicListItem":{"type":"object","required":["id","text","url","note"],"properties":{"id":{"type":"string"},"text":{"type":"string"},"url":{"type":["string","null"],"format":"uri","description":"Link, when the item is a linked item."},"note":{"type":["string","null"]},"items":{"type":"array","description":"Sub-items (one level).","items":{"$ref":"#/components/schemas/PublicListItem"}}}},"PublicList":{"type":"object","required":["id","title","tags","owner","url","markdownUrl","itemCount","followerCount","forkCount","updatedAt","items"],"properties":{"id":{"type":"string"},"title":{"type":"string"},"emoji":{"type":["string","null"]},"description":{"type":["string","null"]},"tags":{"type":"array","items":{"type":"string"}},"lang":{"type":["string","null"],"description":"Detected content language (en, es) or null."},"owner":{"type":"object","required":["handle","url"],"properties":{"handle":{"type":"string"},"url":{"type":"string","format":"uri"}}},"url":{"type":"string","format":"uri","description":"Canonical page; cite this."},"markdownUrl":{"type":"string","format":"uri"},"itemCount":{"type":"integer"},"followerCount":{"type":"integer"},"forkCount":{"type":"integer"},"updatedAt":{"type":"string","format":"date-time"},"items":{"type":"array","items":{"$ref":"#/components/schemas/PublicListItem"}}}},"TagHub":{"type":"object","required":["tag","label","url","markdownUrl","listCount","lists"],"properties":{"tag":{"type":"string"},"label":{"type":"string"},"url":{"type":"string","format":"uri"},"markdownUrl":{"type":"string","format":"uri"},"listCount":{"type":"integer"},"lists":{"type":"array","items":{"type":"object","required":["id","title","url","apiUrl","itemCount","followerCount"],"properties":{"id":{"type":"string"},"title":{"type":"string"},"description":{"type":["string","null"]},"url":{"type":"string","format":"uri"},"apiUrl":{"type":"string","format":"uri","description":"getList URL for this list."},"itemCount":{"type":"integer"},"followerCount":{"type":"integer"}}}}}},"CreateListResponse":{"type":"object","required":["id","message"],"properties":{"id":{"type":"string","description":"List id."},"claimUrl":{"type":"string","format":"uri","description":"Keyless flow only. Secret URL: the only way to view and keep the list. Show it to the user only."},"url":{"type":"string","format":"uri","description":"OAuth flow only: the created list's page in the user's account."},"expiresAt":{"type":"integer","description":"Keyless flow only: unix ms after which an unclaimed draft is purged."},"shareUrl":{"type":"string","format":"uri","description":"Present when allowed_emails was set (keyless flow): the URL the allowlisted people open — share THIS with them, never the claim URL."},"allowedEmails":{"type":"array","items":{"type":"string","format":"email"},"description":"Echo of the accepted (normalized) guest list, when allowed_emails was set."},"message":{"type":"string","description":"Instructions to relay to the user."}}},"MyLists":{"type":"object","required":["lists"],"properties":{"lists":{"type":"array","items":{"type":"object","required":["id","title","isPublic","itemCount","followerCount","url","updatedAt"],"properties":{"id":{"type":"string"},"title":{"type":"string"},"emoji":{"type":["string","null"]},"isPublic":{"type":"boolean"},"itemCount":{"type":"integer"},"followerCount":{"type":"integer"},"url":{"type":"string","format":"uri"},"updatedAt":{"type":"string","format":"date-time"}}}}}}}}}