{"openapi":"3.0.3","info":{"title":"The Loom of Fate API","version":"1.0.0","description":"The REST API of The Loom of Fate. Records 1–20 are shared and read-only; the records you create are yours alone. Write requests need a token (POST /auth/login) or a personal API key (X-API-Key). The Stage server is a second environment with its own records and its own sign-in token."},"servers":[{"url":"https://rest.qasteps.com/api","description":"Test"},{"url":"https://rest.qasteps.com/stage/api","description":"Stage"}],"tags":[{"name":"Service","description":"Routes that need no account"},{"name":"Accounts","description":"Create an account and sign in"},{"name":"Records","description":"The records of the Loom"},{"name":"Gates","description":"Closed doors: HTTP Basic and API keys"},{"name":"Forge","description":"A job that takes a few seconds"},{"name":"Keys","description":"Your personal API keys"},{"name":"Me","description":"Your tasks and your data"}],"paths":{"/hello":{"get":{"tags":["Service"],"summary":"Say hello","operationId":"hello","responses":{"200":{"description":"A greeting","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"}}}}}}}}},"/oracle":{"get":{"tags":["Service"],"summary":"Ask the oracle (answers in about 1.5 seconds)","operationId":"oracle","responses":{"200":{"description":"The answer","content":{"application/json":{"schema":{"type":"object","properties":{"answer":{"type":"string"},"waited_ms":{"type":"integer"}}}}}}}}},"/scrolls":{"get":{"tags":["Service"],"summary":"List the scrolls","operationId":"listScrolls","responses":{"200":{"description":"The scrolls","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"title":{"type":"string"},"lines":{"type":"integer"}}}},"total":{"type":"integer"}}}}}}}}},"/auth/register":{"post":{"tags":["Accounts"],"summary":"Create an account","operationId":"register","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["username","password"],"properties":{"username":{"type":"string","pattern":"^[A-Za-z0-9_]{3,15}$"},"password":{"type":"string","minLength":8,"maxLength":72}}},"example":{"username":"your_name","password":"choose-a-password"}}}},"responses":{"201":{"description":"Account created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Session"}}}},"400":{"description":"A rule is broken: username 3–15 letters, digits or _, password 8–72 characters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The username is taken","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/auth/login":{"post":{"tags":["Accounts"],"summary":"Sign in: a token for 8 hours","operationId":"login","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["username","password"],"properties":{"username":{"type":"string"},"password":{"type":"string"}}},"example":{"username":"your_name","password":"choose-a-password"}}}},"responses":{"200":{"description":"Signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Session"}}}},"401":{"description":"Wrong username or password","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/records":{"get":{"tags":["Records"],"summary":"List records","operationId":"listRecords","parameters":[{"name":"type","in":"query","schema":{"type":"string","enum":["hero","god","creature","titan","mortal"]},"description":"Only records of this kind (exact match)"},{"name":"sort","in":"query","schema":{"type":"string","enum":["id","name_asc","name_desc","newest"],"default":"id"}},{"name":"page","in":"query","schema":{"type":"integer","minimum":1,"default":1}},{"name":"page_size","in":"query","schema":{"type":"integer","minimum":1,"maximum":50,"default":20}}],"responses":{"200":{"description":"One page of records","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Record"}},"page":{"type":"integer"},"page_size":{"type":"integer"},"total":{"type":"integer"}}}}}},"400":{"description":"An unknown sort or a bad page number","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Records"],"summary":"Create a record","operationId":"createRecord","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","type"],"properties":{"name":{"type":"string","minLength":2,"maxLength":40,"example":"Achilles"},"type":{"type":"string","enum":["hero","god","creature","titan","mortal"],"example":"hero"},"home":{"type":"string","maxLength":40,"nullable":true,"example":"Phthia"},"heads":{"type":"integer","minimum":0,"maximum":100,"nullable":true,"example":1},"deeds":{"type":"array","maxItems":10,"items":{"type":"string","minLength":1,"maxLength":60},"example":["Fought at Troy"]}}},"example":{"name":"Ariadne","type":"mortal","home":"Crete","heads":1,"deeds":["Gave Theseus the thread"]}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Record"}}}},"400":{"description":"A field breaks the schema","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/records/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1},"example":7}],"get":{"tags":["Records"],"summary":"Get a record","operationId":"getRecord","responses":{"200":{"description":"The record","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Record"}}}},"400":{"description":"The id is not a whole number from 1","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such record","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"tags":["Records"],"summary":"Replace one of your records","operationId":"replaceRecord","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"description":"Send the whole record: an optional field you leave out is emptied.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","type"],"properties":{"name":{"type":"string","minLength":2,"maxLength":40,"example":"Achilles"},"type":{"type":"string","enum":["hero","god","creature","titan","mortal"],"example":"hero"},"home":{"type":"string","maxLength":40,"nullable":true,"example":"Phthia"},"heads":{"type":"integer","minimum":0,"maximum":100,"nullable":true,"example":1},"deeds":{"type":"array","maxItems":10,"items":{"type":"string","minLength":1,"maxLength":60},"example":["Fought at Troy"]}}},"example":{"name":"Ariadne","type":"mortal","home":"Crete","heads":1,"deeds":["Gave Theseus the thread"]}}}},"responses":{"200":{"description":"Replaced","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Record"}}}},"400":{"description":"A field breaks the schema","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No token or key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"A shared record","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such record of yours","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The name is taken","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Records"],"summary":"Change fields of one of your records","operationId":"updateRecord","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":2,"maxLength":40,"example":"Achilles"},"type":{"type":"string","enum":["hero","god","creature","titan","mortal"],"example":"hero"},"home":{"type":"string","maxLength":40,"nullable":true,"example":"Phthia"},"heads":{"type":"integer","minimum":0,"maximum":100,"nullable":true,"example":1},"deeds":{"type":"array","maxItems":10,"items":{"type":"string","minLength":1,"maxLength":60},"example":["Fought at Troy"]}}},"example":{"home":"Naxos"}}}},"responses":{"200":{"description":"Changed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Record"}}}},"400":{"description":"A field breaks the schema","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No token or key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"A shared record","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such record of yours","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The name is taken","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Records"],"summary":"Delete one of your records","operationId":"deleteRecord","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"responses":{"204":{"description":"Deleted, no body"},"401":{"description":"No token or key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"A shared record","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such record of yours","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/styx":{"get":{"tags":["Gates"],"summary":"Pass the gate (HTTP Basic)","operationId":"passGate","security":[{"basicAuth":[]}],"responses":{"200":{"description":"The gate is open","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"}}}}}},"401":{"description":"No or wrong credentials; the answer carries WWW-Authenticate","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/archives":{"get":{"tags":["Gates"],"summary":"Open the archives (API key only)","operationId":"openArchives","security":[{"apiKeyAuth":[]}],"responses":{"200":{"description":"Opened","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"key_owner":{"type":"string"}}}}}},"401":{"description":"No key, an unknown or revoked key, or a key in the URL","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/forge":{"post":{"tags":["Forge"],"summary":"Start forging","operationId":"startForge","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"responses":{"202":{"description":"Accepted: poll the job","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Job"}}}},"401":{"description":"No token or key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/forge/{job_id}":{"get":{"tags":["Forge"],"summary":"Check a job","operationId":"getJob","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"forging, then ready","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Job"}}}},"401":{"description":"No token or key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such job of yours","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/keys":{"get":{"tags":["Keys"],"summary":"Your API keys","operationId":"listKeys","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Your keys (without the secret)"},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Keys"],"summary":"Create an API key (shown once)","operationId":"createKey","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"}}},"example":{"name":"Postman"}}}},"responses":{"201":{"description":"The key"},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Keys are made with a token, not with a key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/keys/{id}":{"delete":{"tags":["Keys"],"summary":"Revoke an API key","operationId":"revokeKey","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"204":{"description":"Revoked"},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/tasks":{"get":{"tags":["Me"],"summary":"Your tasks and their codes","operationId":"listTasks","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"responses":{"200":{"description":"Tasks"},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/me/reset":{"post":{"tags":["Me"],"summary":"Reset my data","operationId":"resetMyData","security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"responses":{"200":{"description":"Your records and task progress are deleted; the account stays"},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"},"apiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"basicAuth":{"type":"http","scheme":"basic"}},"schemas":{"Record":{"type":"object","properties":{"id":{"type":"integer","example":7},"name":{"type":"string","minLength":2,"maxLength":40,"example":"Achilles"},"type":{"type":"string","enum":["hero","god","creature","titan","mortal"],"example":"hero"},"home":{"type":"string","maxLength":40,"nullable":true,"example":"Phthia"},"heads":{"type":"integer","minimum":0,"maximum":100,"nullable":true,"example":1},"deeds":{"type":"array","maxItems":10,"items":{"type":"string","minLength":1,"maxLength":60},"example":["Fought at Troy"]},"shared":{"type":"boolean","description":"true for records 1–20, which nobody can change"},"created_at":{"type":"string","format":"date-time"}}},"Session":{"type":"object","properties":{"token":{"type":"string"},"user":{"type":"object","properties":{"id":{"type":"integer"},"username":{"type":"string"},"created_at":{"type":"string","format":"date-time"}}}}},"Job":{"type":"object","properties":{"job_id":{"type":"string"},"status":{"type":"string","enum":["forging","ready"]},"item":{"type":"string"},"ready_at":{"type":"string","format":"date-time"}}},"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}}},"trace_id":{"type":"string"}}}}}}