{"openapi":"3.1.0","info":{"title":"Needhave","description":"One public list. Two posts: need and have. No accounts.","version":"1.0.0"},"servers":[{"url":"https://needhave.io"}],"paths":{"/posts":{"get":{"summary":"List posts","description":"Public list. Newest first. No secrets. No messages.","responses":{"200":{"description":"Public list","content":{"application/json":{"schema":{"type":"object","required":["posts"],"properties":{"posts":{"type":"array","items":{"$ref":"#/components/schemas/PublicPost"}}},"additionalProperties":false}}}}}},"post":{"summary":"Create a post","description":"Secret is in this response only.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["kind","note"],"properties":{"kind":{"type":"string","enum":["need","have"]},"note":{"type":"string"}}}}}},"responses":{"201":{"description":"Created post","content":{"application/json":{"schema":{"type":"object","required":["id","kind","note","secret"],"properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["need","have"]},"note":{"type":"string"},"secret":{"type":"string"}},"additionalProperties":false}}}},"400":{"description":"bad_kind, empty_note, or huge_note","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"duplicate_note","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/posts/{id}":{"get":{"summary":"Get one post","description":"One public post. No secret. No messages.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Public post","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicPost"}}}},"404":{"description":"not_found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/posts/{id}/messages":{"get":{"summary":"Public messages on a post","description":"Always empty. Waiting first messages and accepted threads are not listed here.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Empty public message list","content":{"application/json":{"schema":{"type":"object","required":["messages"],"properties":{"messages":{"type":"array","maxItems":0,"items":{"$ref":"#/components/schemas/Message"}}},"additionalProperties":false}}}},"404":{"description":"not_found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Reply","description":"First message from a replier. Reply secret is in this response only. The message stays hidden from anyone without the post secret.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["text"],"properties":{"text":{"type":"string"}}}}}},"responses":{"201":{"description":"Hidden first message","content":{"application/json":{"schema":{"type":"object","required":["id","post_id","hidden","secret"],"properties":{"id":{"type":"string"},"post_id":{"type":"string"},"hidden":{"type":"boolean"},"secret":{"type":"string"}},"additionalProperties":false}}}},"400":{"description":"empty_note or huge_note","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"too_many or rate_limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/posts/{id}/waiting":{"post":{"summary":"Read waiting first messages","description":"Poster reads waiting first messages with the post secret. Accepted first messages are not listed.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["secret"],"properties":{"secret":{"type":"string"}}}}}},"responses":{"200":{"description":"Waiting first messages","content":{"application/json":{"schema":{"type":"object","required":["messages"],"properties":{"messages":{"type":"array","items":{"$ref":"#/components/schemas/Message"}}},"additionalProperties":false}}}},"400":{"description":"bad_request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"bad_secret","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/posts/{id}/accept":{"post":{"summary":"Accept a first message","description":"Poster accepts one first message with the post secret. Writes one thread key. The replier uses POST /messages/{id}/thread.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["secret","message_id"],"properties":{"secret":{"type":"string"},"message_id":{"type":"string"}}}}}},"responses":{"201":{"description":"Accepted","content":{"application/json":{"schema":{"type":"object","required":["thread_key"],"properties":{"thread_key":{"type":"string"}},"additionalProperties":false}}}},"400":{"description":"bad_request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"bad_secret","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"already_accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/messages/{id}/thread":{"post":{"summary":"Claim a thread key","description":"Replier calls back with the reply secret. Before accept: accepted is false and there is no thread_key. After accept: accepted is true and thread_key is set.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["secret"],"properties":{"secret":{"type":"string"}}}}}},"responses":{"200":{"description":"Thread claim","content":{"application/json":{"schema":{"type":"object","required":["accepted"],"properties":{"accepted":{"type":"boolean"},"thread_key":{"type":"string"}},"additionalProperties":false}}}},"400":{"description":"bad_request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"bad_secret","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/threads":{"post":{"summary":"Read a thread","description":"First message, then later messages, oldest first. The thread key is in the JSON body, the same way the post secret already is. A path that still contains the key does not return the conversation.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["thread_key"],"properties":{"thread_key":{"type":"string"}},"additionalProperties":false}}}},"responses":{"200":{"description":"Thread","content":{"application/json":{"schema":{"type":"object","required":["post_id","messages"],"properties":{"post_id":{"type":"string"},"messages":{"type":"array","items":{"$ref":"#/components/schemas/Message"}}},"additionalProperties":false}}}},"400":{"description":"bad_request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/threads/messages":{"post":{"summary":"Send a later message","description":"Later message on that thread. The thread key is in the JSON body. The poster uses the key from accept. The replier uses the key from POST /messages/{id}/thread after accept. A path that still contains the key does not accept a message.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["thread_key","text"],"properties":{"thread_key":{"type":"string"},"text":{"type":"string"}},"additionalProperties":false}}}},"responses":{"201":{"description":"Later message","content":{"application/json":{"schema":{"type":"object","required":["id","post_id"],"properties":{"id":{"type":"string"},"post_id":{"type":"string"}},"additionalProperties":false}}}},"400":{"description":"bad_request, empty_note, or huge_note","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"not_found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}},"additionalProperties":false},"PublicPost":{"type":"object","required":["id","kind","note"],"properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["need","have"]},"note":{"type":"string"}},"additionalProperties":false},"Message":{"type":"object","required":["id","text"],"properties":{"id":{"type":"string"},"text":{"type":"string"}},"additionalProperties":false}}}}