{"openapi":"3.1.0","info":{"title":"Blupinta API","version":"1","description":"The public API for apps built on Blupinta: a home dashboard, another developer’s service. An app reads what the person allowed it, with an access token from auth.blupinta.app (OAuth 2.1 and OpenID Connect). A device without a keyboard uses the device flow (`https://auth.blupinta.app/oidc/device/auth`, client `device`, no registration); an app with a browser uses the authorization code with PKCE. Every request carries the token as `Authorization: Bearer …`. `/v1` only grows: fields and operations may be added, never removed or changed.","license":{"name":"Apache-2.0","identifier":"Apache-2.0"}},"externalDocs":{"description":"The guide for developers","url":"https://docs.blupinta.app/reference/public-api/"},"servers":[{"url":"https://api.blupinta.app"}],"tags":[{"name":"API","description":"What the API is"},{"name":"Projects","description":"The projects an app may read and their saved versions"},{"name":"Files","description":"The files the saved versions name: drawings, images, products’ models"},{"name":"Homes","description":"Home files, checked by Blupinta"},{"name":"Comments","description":"The conversations pinned on a project’s plans"},{"name":"Catalog","description":"Blupinta’s catalog of products, searched and read"},{"name":"Webhooks","description":"An app told at an address of its own what happens to its projects: each event a `V1WebhookBody`"},{"name":"Host apps","description":"What an app declares about a project: the elements it needs, its devices"}],"paths":{"/v1":{"get":{"operationId":"getApiIndex","tags":["API"],"summary":"What the API is","description":"Where tokens come from, the resource they must be for, the scopes, every operation and where they are explained.","security":[],"responses":{"200":{"description":"What the API is","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiIndex"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"429":{"description":"`rate_limited`: Too many requests: wait as long as `Retry-After` says","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}}},"500":{"description":"`server_error`: Something failed on Blupinta’s side: quote `errorId` when asking for help","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}}}}},"/v1/openapi.json":{"get":{"operationId":"getOpenApiDocument","tags":["API"],"summary":"This OpenAPI document","description":"Every operation of the API, its parameters, answers and refusals, generated from the server.","security":[],"responses":{"200":{"description":"This OpenAPI document","content":{"application/json":{"schema":{"type":"object","description":"An OpenAPI 3.1 document"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"429":{"description":"`rate_limited`: Too many requests: wait as long as `Retry-After` says","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}}},"500":{"description":"`server_error`: Something failed on Blupinta’s side: quote `errorId` when asking for help","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}}}}},"/v1/homes/check":{"post":{"operationId":"checkHome","tags":["Homes"],"summary":"Check a home file","description":"What Blupinta’s own checks say of a home file, of any version of the format: brought to the current one as an import would, read, and checked as saving a version is. Nothing is kept. A file that isn’t a home answers `format` problems, each with its path.","security":[{"blupinta":["projects:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1HomeCheckRequest"}}}},"responses":{"200":{"description":"Check a home file","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1HomeCheck"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"400":{"description":"`invalid_input`: What was sent isn’t right: `problems` says what, each with where","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"401":{"description":"`unauthenticated`: No valid access token for this API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"403":{"description":"`forbidden`: The token lacks the scope this operation needs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"429":{"description":"`rate_limited`: Too many requests: wait as long as `Retry-After` says","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}}},"500":{"description":"`server_error`: Something failed on Blupinta’s side: quote `errorId` when asking for help","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}}}}},"/v1/projects":{"post":{"operationId":"createProject","tags":["Projects"],"summary":"Create a project","description":"A new project in the person’s account, its home empty and in their language; the app may then read and write it as one of its own projects.","security":[{"blupinta":["projects:create"]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Any unique text of the app’s, the same for a retry: the request is acted on once, its answer kept a day","schema":{"type":"string","maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1ProjectInput"}}}},"responses":{"201":{"description":"Create a project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Project"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"400":{"description":"`invalid_input`: What was sent isn’t right: `problems` says what, each with where","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"401":{"description":"`unauthenticated`: No valid access token for this API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"403":{"description":"`forbidden`: The token lacks the scope this operation needs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"429":{"description":"`rate_limited`: Too many requests: wait as long as `Retry-After` says","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}}},"500":{"description":"`server_error`: Something failed on Blupinta’s side: quote `errorId` when asking for help","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}}}},"get":{"operationId":"listProjects","tags":["Projects"],"summary":"The projects it may read","description":"The projects the person chose when connecting the app, by name, each with its last saved version.","security":[{"blupinta":["projects:read"]}],"responses":{"200":{"description":"The projects it may read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1ProjectPage"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"401":{"description":"`unauthenticated`: No valid access token for this API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"403":{"description":"`forbidden`: The token lacks the scope this operation needs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"429":{"description":"`rate_limited`: Too many requests: wait as long as `Retry-After` says","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}}},"500":{"description":"`server_error`: Something failed on Blupinta’s side: quote `errorId` when asking for help","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}}}}},"/v1/projects/{projectId}":{"get":{"operationId":"getProject","tags":["Projects"],"summary":"One project","description":"One of the projects it may read: its name, its address and its last saved version.","security":[{"blupinta":["projects:read"]}],"parameters":[{"name":"projectId","in":"path","required":true,"description":"One of the projects the person chose when connecting the app","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"One project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Project"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"401":{"description":"`unauthenticated`: No valid access token for this API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"403":{"description":"`forbidden`: The token lacks the scope this operation needs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"404":{"description":"`not_found`: Nothing there, or nothing this token may read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"429":{"description":"`rate_limited`: Too many requests: wait as long as `Retry-After` says","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}}},"500":{"description":"`server_error`: Something failed on Blupinta’s side: quote `errorId` when asking for help","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}}}},"patch":{"operationId":"renameProject","tags":["Projects"],"summary":"Rename a project","description":"Its name and address, as its people read them.","security":[{"blupinta":["projects:write"]}],"parameters":[{"name":"projectId","in":"path","required":true,"description":"One of the projects the person chose when connecting the app","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1ProjectInput"}}}},"responses":{"200":{"description":"Rename a project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Project"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"400":{"description":"`invalid_input`: What was sent isn’t right: `problems` says what, each with where","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"401":{"description":"`unauthenticated`: No valid access token for this API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"403":{"description":"`forbidden`: The token lacks the scope this operation needs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"404":{"description":"`not_found`: Nothing there, or nothing this token may read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"429":{"description":"`rate_limited`: Too many requests: wait as long as `Retry-After` says","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}}},"500":{"description":"`server_error`: Something failed on Blupinta’s side: quote `errorId` when asking for help","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}}}}},"/v1/projects/{projectId}/home":{"get":{"operationId":"getHome","tags":["Projects"],"summary":"The last version saved","description":"The last saved version of the project with its home. It carries an ETag: send it back in `If-None-Match` and an unchanged home answers 304 with no body. A project with no version saved yet answers `not_found`.","security":[{"blupinta":["projects:read"]}],"parameters":[{"name":"projectId","in":"path","required":true,"description":"One of the projects the person chose when connecting the app","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The last version saved","headers":{"ETag":{"description":"Send it back in `If-None-Match`","schema":{"type":"string"}},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Revision"}}}},"304":{"description":"Unchanged since the ETag sent in `If-None-Match`","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"401":{"description":"`unauthenticated`: No valid access token for this API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"403":{"description":"`forbidden`: The token lacks the scope this operation needs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"404":{"description":"`not_found`: Nothing there, or nothing this token may read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"429":{"description":"`rate_limited`: Too many requests: wait as long as `Retry-After` says","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}}},"500":{"description":"`server_error`: Something failed on Blupinta’s side: quote `errorId` when asking for help","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}}}}},"/v1/projects/{projectId}/revisions":{"get":{"operationId":"listRevisions","tags":["Projects"],"summary":"Every version saved","description":"The saved versions of the project, newest first, without their homes.","security":[{"blupinta":["projects:read"]}],"parameters":[{"name":"projectId","in":"path","required":true,"description":"One of the projects the person chose when connecting the app","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Every version saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1RevisionPage"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"401":{"description":"`unauthenticated`: No valid access token for this API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"403":{"description":"`forbidden`: The token lacks the scope this operation needs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"404":{"description":"`not_found`: Nothing there, or nothing this token may read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"429":{"description":"`rate_limited`: Too many requests: wait as long as `Retry-After` says","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}}},"500":{"description":"`server_error`: Something failed on Blupinta’s side: quote `errorId` when asking for help","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}}}},"post":{"operationId":"saveRevision","tags":["Projects"],"summary":"Save a version","description":"Saves a home as the project’s next version, held to everything Save version is held to in the editor (the format, the checks, the files it names, what connected apps need). It starts from `baseRevision`: another version saved since is a `conflict`, nothing overwritten. The draft the editor works on is left as it is; the editors open on the project learn of the new version, which says the app saved it.","security":[{"blupinta":["projects:write"]}],"parameters":[{"name":"projectId","in":"path","required":true,"description":"One of the projects the person chose when connecting the app","schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Any unique text of the app’s, the same for a retry: the request is acted on once, its answer kept a day","schema":{"type":"string","maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1SaveRevisionRequest"}}}},"responses":{"201":{"description":"Save a version","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1RevisionRow"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"400":{"description":"`invalid_input`: What was sent isn’t right: `problems` says what, each with where","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"401":{"description":"`unauthenticated`: No valid access token for this API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"403":{"description":"`forbidden`: The token lacks the scope this operation needs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"404":{"description":"`not_found`: Nothing there, or nothing this token may read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"409":{"description":"`conflict`: Another version was saved since the one the request starts from: `problems` names the last","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"422":{"description":"`invalid_home`: The home isn’t one of the format, or names files the project lacks: `problems` says what; `blocking_problems`: The home can’t be saved as a version: `problems` lists why (as `POST /v1/homes/check` does)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"429":{"description":"`rate_limited`: Too many requests: wait as long as `Retry-After` says","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}}},"500":{"description":"`server_error`: Something failed on Blupinta’s side: quote `errorId` when asking for help","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}}}}},"/v1/projects/{projectId}/revisions/{number}":{"get":{"operationId":"getRevision","tags":["Projects"],"summary":"One version saved","description":"One saved version of the project, with its home.","security":[{"blupinta":["projects:read"]}],"parameters":[{"name":"projectId","in":"path","required":true,"description":"One of the projects the person chose when connecting the app","schema":{"type":"string","format":"uuid"}},{"name":"number","in":"path","required":true,"description":"The version’s number","schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"One version saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Revision"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"401":{"description":"`unauthenticated`: No valid access token for this API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"403":{"description":"`forbidden`: The token lacks the scope this operation needs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"404":{"description":"`not_found`: Nothing there, or nothing this token may read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"429":{"description":"`rate_limited`: Too many requests: wait as long as `Retry-After` says","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}}},"500":{"description":"`server_error`: Something failed on Blupinta’s side: quote `errorId` when asking for help","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}}}}},"/v1/projects/{projectId}/events":{"get":{"operationId":"projectEvents","tags":["Projects"],"summary":"What happens to the project, as it happens","description":"A stream of server-sent events (`text/event-stream`) that stays open. `revision.saved` (data: `V1RevisionSaved`) when a version is saved: read it then. `access.revoked` when the app is disconnected or loses the project, `token.expired` when the access token runs out: the stream ends after either, and the app reconnects with a fresh token (or not at all, once revoked). A comment line every 25 seconds keeps it open through proxies.","security":[{"blupinta":["projects:read"]}],"parameters":[{"name":"projectId","in":"path","required":true,"description":"One of the projects the person chose when connecting the app","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Server-sent events: `revision.saved` (its data a `V1RevisionSaved`), then `access.revoked` or `token.expired` before the stream ends","content":{"text/event-stream":{"schema":{"type":"string"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"401":{"description":"`unauthenticated`: No valid access token for this API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"403":{"description":"`forbidden`: The token lacks the scope this operation needs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"404":{"description":"`not_found`: Nothing there, or nothing this token may read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"429":{"description":"`rate_limited`: Too many requests: wait as long as `Retry-After` says","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}}},"500":{"description":"`server_error`: Something failed on Blupinta’s side: quote `errorId` when asking for help","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}}}}},"/v1/projects/{projectId}/thumbnail":{"get":{"operationId":"getThumbnail","tags":["Files"],"summary":"The last version’s thumbnail","description":"The plan seen from above, as the editor drew it when the last version was saved (WebP). A redirect to the CDN, as for files; `not_found` when that version has none (saved before thumbnails, or by another client).","security":[{"blupinta":["projects:read"]}],"parameters":[{"name":"projectId","in":"path","required":true,"description":"One of the projects the person chose when connecting the app","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The file’s bytes, where no CDN is set (a development server)","content":{"application/octet-stream":{"schema":{"type":"string","contentMediaType":"application/octet-stream"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"302":{"description":"The file on Blupinta’s CDN, signed for a few minutes: follow the redirect","headers":{"Location":{"description":"Where the file is","schema":{"type":"string","format":"uri"}},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"401":{"description":"`unauthenticated`: No valid access token for this API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"403":{"description":"`forbidden`: The token lacks the scope this operation needs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"404":{"description":"`not_found`: Nothing there, or nothing this token may read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"429":{"description":"`rate_limited`: Too many requests: wait as long as `Retry-After` says","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}}},"500":{"description":"`server_error`: Something failed on Blupinta’s side: quote `errorId` when asking for help","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}}}}},"/v1/projects/{projectId}/files/{sha256}":{"get":{"operationId":"getFile","tags":["Files"],"summary":"A file a version names","description":"A file of the project that one of its saved versions names by its sha256 (a drawing, a floor’s image). The answer is a redirect to the file on Blupinta’s CDN, signed for a few minutes: follow it. Files never change, so keep them as long as you like.","security":[{"blupinta":["projects:read"]}],"parameters":[{"name":"projectId","in":"path","required":true,"description":"One of the projects the person chose when connecting the app","schema":{"type":"string","format":"uuid"}},{"name":"sha256","in":"path","required":true,"description":"The file’s sha256, as the home names it","schema":{"type":"string","pattern":"^[0-9a-f]{64}$"}}],"responses":{"200":{"description":"The file’s bytes, where no CDN is set (a development server)","content":{"application/octet-stream":{"schema":{"type":"string","contentMediaType":"application/octet-stream"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"302":{"description":"The file on Blupinta’s CDN, signed for a few minutes: follow the redirect","headers":{"Location":{"description":"Where the file is","schema":{"type":"string","format":"uri"}},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"401":{"description":"`unauthenticated`: No valid access token for this API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"403":{"description":"`forbidden`: The token lacks the scope this operation needs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"404":{"description":"`not_found`: Nothing there, or nothing this token may read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"429":{"description":"`rate_limited`: Too many requests: wait as long as `Retry-After` says","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}}},"500":{"description":"`server_error`: Something failed on Blupinta’s side: quote `errorId` when asking for help","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}}}}},"/v1/projects/{projectId}/files":{"post":{"operationId":"uploadFile","tags":["Files"],"summary":"Upload a file","description":"A drawing or an image for the project’s home: the body is the file’s bytes, `content-type` says what they are (PNG, JPEG, WebP or PDF, checked against the bytes) and `?name=` its name. Its `sha256` is how a home names it. The same bytes again are the same file (200, not 201).","security":[{"blupinta":["projects:write"]}],"parameters":[{"name":"projectId","in":"path","required":true,"description":"One of the projects the person chose when connecting the app","schema":{"type":"string","format":"uuid"}},{"name":"name","in":"query","required":false,"description":"The file’s name, as people read it; `file` without","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"image/png":{"schema":{"type":"string","contentMediaType":"image/png"}},"image/jpeg":{"schema":{"type":"string","contentMediaType":"image/jpeg"}},"image/webp":{"schema":{"type":"string","contentMediaType":"image/webp"}},"application/pdf":{"schema":{"type":"string","contentMediaType":"application/pdf"}}}},"responses":{"200":{"description":"The project had the same file already","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1File"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"201":{"description":"Upload a file","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1File"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"401":{"description":"`unauthenticated`: No valid access token for this API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"403":{"description":"`forbidden`: The token lacks the scope this operation needs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"404":{"description":"`not_found`: Nothing there, or nothing this token may read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"415":{"description":"`unsupported_file`: Not a PNG, JPEG, WebP or PDF, or its bytes aren’t the type it says","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"429":{"description":"`rate_limited`: Too many requests: wait as long as `Retry-After` says","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}}},"500":{"description":"`server_error`: Something failed on Blupinta’s side: quote `errorId` when asking for help","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}}}}},"/v1/projects/{projectId}/catalog/models/{sha256}":{"get":{"operationId":"getCatalogModel","tags":["Files"],"summary":"A product’s 3D model","description":"The 3D model (glTF binary) of a catalog product one of the project’s saved versions furnishes it with, by the sha256 its product names. A redirect to the CDN, as for files.","security":[{"blupinta":["projects:read"]}],"parameters":[{"name":"projectId","in":"path","required":true,"description":"One of the projects the person chose when connecting the app","schema":{"type":"string","format":"uuid"}},{"name":"sha256","in":"path","required":true,"description":"The file’s sha256, as the home names it","schema":{"type":"string","pattern":"^[0-9a-f]{64}$"}}],"responses":{"200":{"description":"The file’s bytes, where no CDN is set (a development server)","content":{"application/octet-stream":{"schema":{"type":"string","contentMediaType":"application/octet-stream"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"302":{"description":"The file on Blupinta’s CDN, signed for a few minutes: follow the redirect","headers":{"Location":{"description":"Where the file is","schema":{"type":"string","format":"uri"}},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"401":{"description":"`unauthenticated`: No valid access token for this API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"403":{"description":"`forbidden`: The token lacks the scope this operation needs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"404":{"description":"`not_found`: Nothing there, or nothing this token may read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"429":{"description":"`rate_limited`: Too many requests: wait as long as `Retry-After` says","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}}},"500":{"description":"`server_error`: Something failed on Blupinta’s side: quote `errorId` when asking for help","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}}}}},"/v1/projects/{projectId}/catalog/thumbnails/{sha256}":{"get":{"operationId":"getCatalogThumbnail","tags":["Files"],"summary":"A product’s thumbnail","description":"The thumbnail (WebP) of a catalog product one of the project’s saved versions names. A redirect to the CDN, as for files.","security":[{"blupinta":["projects:read"]}],"parameters":[{"name":"projectId","in":"path","required":true,"description":"One of the projects the person chose when connecting the app","schema":{"type":"string","format":"uuid"}},{"name":"sha256","in":"path","required":true,"description":"The file’s sha256, as the home names it","schema":{"type":"string","pattern":"^[0-9a-f]{64}$"}}],"responses":{"200":{"description":"The file’s bytes, where no CDN is set (a development server)","content":{"application/octet-stream":{"schema":{"type":"string","contentMediaType":"application/octet-stream"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"302":{"description":"The file on Blupinta’s CDN, signed for a few minutes: follow the redirect","headers":{"Location":{"description":"Where the file is","schema":{"type":"string","format":"uri"}},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"401":{"description":"`unauthenticated`: No valid access token for this API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"403":{"description":"`forbidden`: The token lacks the scope this operation needs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"404":{"description":"`not_found`: Nothing there, or nothing this token may read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"429":{"description":"`rate_limited`: Too many requests: wait as long as `Retry-After` says","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}}},"500":{"description":"`server_error`: Something failed on Blupinta’s side: quote `errorId` when asking for help","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}}}}},"/v1/projects/{projectId}/export":{"get":{"operationId":"exportProject","tags":["Projects"],"summary":"Every version, as a zip","description":"A zip of the project’s saved versions and the files they name: `project.json`, `versions.json` (the list), `versions/<number>.home.json` (each home) and `files/<sha256>.<extension>`.","security":[{"blupinta":["projects:read"]}],"parameters":[{"name":"projectId","in":"path","required":true,"description":"One of the projects the person chose when connecting the app","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Every version, as a zip","content":{"application/zip":{"schema":{"type":"string","contentMediaType":"application/zip"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"401":{"description":"`unauthenticated`: No valid access token for this API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"403":{"description":"`forbidden`: The token lacks the scope this operation needs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"404":{"description":"`not_found`: Nothing there, or nothing this token may read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"429":{"description":"`rate_limited`: Too many requests: wait as long as `Retry-After` says","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}}},"500":{"description":"`server_error`: Something failed on Blupinta’s side: quote `errorId` when asking for help","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}}}}},"/v1/projects/{projectId}/host-references":{"get":{"operationId":"getHostReferences","tags":["Host apps"],"summary":"The elements it declared it needs","description":"What the app declared, for this project, of the elements of the home its code names.","security":[{"blupinta":["hosts:manage"]}],"parameters":[{"name":"projectId","in":"path","required":true,"description":"One of the projects the person chose when connecting the app","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The elements it declared it needs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1HostReferences"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"401":{"description":"`unauthenticated`: No valid access token for this API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"403":{"description":"`forbidden`: The token lacks the scope this operation needs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"404":{"description":"`not_found`: Nothing there, or nothing this token may read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"429":{"description":"`rate_limited`: Too many requests: wait as long as `Retry-After` says","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}}},"500":{"description":"`server_error`: Something failed on Blupinta’s side: quote `errorId` when asking for help","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}}}},"put":{"operationId":"putHostReferences","tags":["Host apps"],"summary":"Declare the elements it needs","description":"Replaces what the app declared it needs of the home: elements by kind and id (a wall the app hangs a picture on, the room a switch belongs to). Every one must be in the last saved version (`invalid_input` names those that aren’t). From then on, saving a version that drops one is refused, naming the app, and the editor shows them as errors. An empty list declares nothing.","security":[{"blupinta":["hosts:manage"]}],"parameters":[{"name":"projectId","in":"path","required":true,"description":"One of the projects the person chose when connecting the app","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1HostReferences"}}}},"responses":{"200":{"description":"Declare the elements it needs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1HostReferences"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"400":{"description":"`invalid_input`: What was sent isn’t right: `problems` says what, each with where","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"401":{"description":"`unauthenticated`: No valid access token for this API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"403":{"description":"`forbidden`: The token lacks the scope this operation needs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"404":{"description":"`not_found`: Nothing there, or nothing this token may read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"429":{"description":"`rate_limited`: Too many requests: wait as long as `Retry-After` says","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}}},"500":{"description":"`server_error`: Something failed on Blupinta’s side: quote `errorId` when asking for help","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}}}}},"/v1/projects/{projectId}/devices":{"get":{"operationId":"getDevices","tags":["Host apps"],"summary":"The devices it declared","description":"The devices the app declared for this project.","security":[{"blupinta":["hosts:manage"]}],"parameters":[{"name":"projectId","in":"path","required":true,"description":"One of the projects the person chose when connecting the app","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The devices it declared","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Devices"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"401":{"description":"`unauthenticated`: No valid access token for this API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"403":{"description":"`forbidden`: The token lacks the scope this operation needs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"404":{"description":"`not_found`: Nothing there, or nothing this token may read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"429":{"description":"`rate_limited`: Too many requests: wait as long as `Retry-After` says","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}}},"500":{"description":"`server_error`: Something failed on Blupinta’s side: quote `errorId` when asking for help","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}}}},"put":{"operationId":"putDevices","tags":["Host apps"],"summary":"Declare its devices","description":"Replaces the devices the app knows for this project (its lights, covers, switches, sensors, climate and media, by its own ids), offered in the editor when binding a light or a shutter of the home.","security":[{"blupinta":["hosts:manage"]}],"parameters":[{"name":"projectId","in":"path","required":true,"description":"One of the projects the person chose when connecting the app","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Devices"}}}},"responses":{"200":{"description":"Declare its devices","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Devices"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"400":{"description":"`invalid_input`: What was sent isn’t right: `problems` says what, each with where","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"401":{"description":"`unauthenticated`: No valid access token for this API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"403":{"description":"`forbidden`: The token lacks the scope this operation needs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"404":{"description":"`not_found`: Nothing there, or nothing this token may read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"429":{"description":"`rate_limited`: Too many requests: wait as long as `Retry-After` says","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}}},"500":{"description":"`server_error`: Something failed on Blupinta’s side: quote `errorId` when asking for help","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}}}}},"/v1/catalog/products":{"get":{"operationId":"searchProducts","tags":["Catalog"],"summary":"Search the catalog","description":"Blupinta’s catalog of products searched as the editor searches it: words, narrowed by category, brand, mount and colour family, in pages.","security":[{"blupinta":["catalog:read"]}],"parameters":[{"name":"q","in":"query","required":false,"description":"Words to find, tolerant of a slip of a letter and of accents","schema":{"type":"string"}},{"name":"category","in":"query","required":false,"description":"A category or any below it (`storage` takes `storage/shelving`)","schema":{"type":"string"}},{"name":"brand","in":"query","required":false,"description":"Any of these brands","schema":{"type":"array","items":{"type":"string"}},"style":"form","explode":true},{"name":"mount","in":"query","required":false,"description":"Where it stands or hangs","schema":{"type":"string","enum":["floor","wall","ceiling","surface","built-in"]}},{"name":"family","in":"query","required":false,"description":"A colour family one of its finishes is in (`white`, `wood`)","schema":{"type":"string"}},{"name":"sort","in":"query","required":false,"description":"The order: by relevance (the default), by name, smallest or largest first","schema":{"type":"string","enum":["relevance","name","smallest","largest"]}},{"name":"language","in":"query","required":false,"description":"The names words and order are read in (`en` by default)","schema":{"type":"string","enum":["it","en"]}},{"name":"limit","in":"query","required":false,"description":"How many on a page, at most 100 (50 by default)","schema":{"type":"integer","minimum":1}},{"name":"after","in":"query","required":false,"description":"Where the page starts: the `next` of the page before","schema":{"type":"string"}}],"responses":{"200":{"description":"Search the catalog","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1ProductPage"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"400":{"description":"`invalid_input`: What was sent isn’t right: `problems` says what, each with where","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"401":{"description":"`unauthenticated`: No valid access token for this API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"403":{"description":"`forbidden`: The token lacks the scope this operation needs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"429":{"description":"`rate_limited`: Too many requests: wait as long as `Retry-After` says","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}}},"500":{"description":"`server_error`: Something failed on Blupinta’s side: quote `errorId` when asking for help","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}}}}},"/v1/catalog/products/{namespace}/{name}":{"get":{"operationId":"getProduct","tags":["Catalog"],"summary":"One product, whole","description":"A product of the catalog as a home holds it among its `products`: its options, sizes as expressions of them, geometry, clearances, what a house entity may drive in it, its thumbnails.","security":[{"blupinta":["catalog:read"]}],"parameters":[{"name":"namespace","in":"path","required":true,"description":"The first part of the product’s id (`ikea`)","schema":{"type":"string","pattern":"^[a-z0-9-]+$"}},{"name":"name","in":"path","required":true,"description":"The second part of the product’s id (`kallax`)","schema":{"type":"string","pattern":"^[a-z0-9-]+$"}}],"responses":{"200":{"description":"One product, whole","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Product"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"401":{"description":"`unauthenticated`: No valid access token for this API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"403":{"description":"`forbidden`: The token lacks the scope this operation needs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"404":{"description":"`not_found`: Nothing there, or nothing this token may read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"429":{"description":"`rate_limited`: Too many requests: wait as long as `Retry-After` says","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}}},"500":{"description":"`server_error`: Something failed on Blupinta’s side: quote `errorId` when asking for help","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}}}}},"/v1/catalog/models/{sha256}":{"get":{"operationId":"catalogModel","tags":["Catalog"],"summary":"A product’s 3D model","description":"Any 3D model (glTF binary) of the catalog, by the sha256 a product names; a redirect to the CDN.","security":[{"blupinta":["catalog:read"]}],"parameters":[{"name":"sha256","in":"path","required":true,"description":"The file’s sha256, as the home names it","schema":{"type":"string","pattern":"^[0-9a-f]{64}$"}}],"responses":{"200":{"description":"The file’s bytes, where no CDN is set (a development server)","content":{"application/octet-stream":{"schema":{"type":"string","contentMediaType":"application/octet-stream"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"302":{"description":"The file on Blupinta’s CDN, signed for a few minutes: follow the redirect","headers":{"Location":{"description":"Where the file is","schema":{"type":"string","format":"uri"}},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"401":{"description":"`unauthenticated`: No valid access token for this API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"403":{"description":"`forbidden`: The token lacks the scope this operation needs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"404":{"description":"`not_found`: Nothing there, or nothing this token may read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"429":{"description":"`rate_limited`: Too many requests: wait as long as `Retry-After` says","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}}},"500":{"description":"`server_error`: Something failed on Blupinta’s side: quote `errorId` when asking for help","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}}}}},"/v1/catalog/thumbnails/{sha256}":{"get":{"operationId":"catalogThumbnail","tags":["Catalog"],"summary":"A product’s thumbnail","description":"Any thumbnail (WebP) of the catalog, by the sha256 a product names; a redirect to the CDN.","security":[{"blupinta":["catalog:read"]}],"parameters":[{"name":"sha256","in":"path","required":true,"description":"The file’s sha256, as the home names it","schema":{"type":"string","pattern":"^[0-9a-f]{64}$"}}],"responses":{"200":{"description":"The file’s bytes, where no CDN is set (a development server)","content":{"application/octet-stream":{"schema":{"type":"string","contentMediaType":"application/octet-stream"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"302":{"description":"The file on Blupinta’s CDN, signed for a few minutes: follow the redirect","headers":{"Location":{"description":"Where the file is","schema":{"type":"string","format":"uri"}},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"401":{"description":"`unauthenticated`: No valid access token for this API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"403":{"description":"`forbidden`: The token lacks the scope this operation needs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"404":{"description":"`not_found`: Nothing there, or nothing this token may read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"429":{"description":"`rate_limited`: Too many requests: wait as long as `Retry-After` says","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}}},"500":{"description":"`server_error`: Something failed on Blupinta’s side: quote `errorId` when asking for help","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}}}}},"/v1/projects/{projectId}/threads":{"get":{"operationId":"listThreads","tags":["Comments"],"summary":"The project’s threads","description":"Every thread pinned on the project’s plans, with its messages, oldest first.","security":[{"blupinta":["comments:read"]}],"parameters":[{"name":"projectId","in":"path","required":true,"description":"One of the projects the person chose when connecting the app","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The project’s threads","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1ThreadPage"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"401":{"description":"`unauthenticated`: No valid access token for this API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"403":{"description":"`forbidden`: The token lacks the scope this operation needs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"404":{"description":"`not_found`: Nothing there, or nothing this token may read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"429":{"description":"`rate_limited`: Too many requests: wait as long as `Retry-After` says","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}}},"500":{"description":"`server_error`: Something failed on Blupinta’s side: quote `errorId` when asking for help","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}}}},"post":{"operationId":"openThread","tags":["Comments"],"summary":"Open a thread","description":"A thread pinned on a plan, on one of its elements or at a point, with its first message, written as the person (anyone who can look at the project may comment); it says the app wrote it.","security":[{"blupinta":["comments:write"]}],"parameters":[{"name":"projectId","in":"path","required":true,"description":"One of the projects the person chose when connecting the app","schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Any unique text of the app’s, the same for a retry: the request is acted on once, its answer kept a day","schema":{"type":"string","maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1ThreadInput"}}}},"responses":{"201":{"description":"Open a thread","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Thread"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"400":{"description":"`invalid_input`: What was sent isn’t right: `problems` says what, each with where","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"401":{"description":"`unauthenticated`: No valid access token for this API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"403":{"description":"`forbidden`: The token lacks the scope this operation needs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"404":{"description":"`not_found`: Nothing there, or nothing this token may read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"429":{"description":"`rate_limited`: Too many requests: wait as long as `Retry-After` says","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}}},"500":{"description":"`server_error`: Something failed on Blupinta’s side: quote `errorId` when asking for help","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}}}}},"/v1/projects/{projectId}/threads/{threadId}/comments":{"post":{"operationId":"answerThread","tags":["Comments"],"summary":"Answer a thread","description":"A message added to the thread; whoever opened it is told.","security":[{"blupinta":["comments:write"]}],"parameters":[{"name":"projectId","in":"path","required":true,"description":"One of the projects the person chose when connecting the app","schema":{"type":"string","format":"uuid"}},{"name":"threadId","in":"path","required":true,"description":"One of the project’s threads","schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Any unique text of the app’s, the same for a retry: the request is acted on once, its answer kept a day","schema":{"type":"string","maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1CommentInput"}}}},"responses":{"201":{"description":"Answer a thread","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Thread"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"400":{"description":"`invalid_input`: What was sent isn’t right: `problems` says what, each with where","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"401":{"description":"`unauthenticated`: No valid access token for this API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"403":{"description":"`forbidden`: The token lacks the scope this operation needs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"404":{"description":"`not_found`: Nothing there, or nothing this token may read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"429":{"description":"`rate_limited`: Too many requests: wait as long as `Retry-After` says","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}}},"500":{"description":"`server_error`: Something failed on Blupinta’s side: quote `errorId` when asking for help","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}}}}},"/v1/projects/{projectId}/threads/{threadId}":{"patch":{"operationId":"resolveThread","tags":["Comments"],"summary":"Resolve a thread, or open it again","description":"Whoever wrote in it is told.","security":[{"blupinta":["comments:write"]}],"parameters":[{"name":"projectId","in":"path","required":true,"description":"One of the projects the person chose when connecting the app","schema":{"type":"string","format":"uuid"}},{"name":"threadId","in":"path","required":true,"description":"One of the project’s threads","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1ThreadChange"}}}},"responses":{"200":{"description":"Resolve a thread, or open it again","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Thread"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"400":{"description":"`invalid_input`: What was sent isn’t right: `problems` says what, each with where","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"401":{"description":"`unauthenticated`: No valid access token for this API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"403":{"description":"`forbidden`: The token lacks the scope this operation needs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"404":{"description":"`not_found`: Nothing there, or nothing this token may read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"429":{"description":"`rate_limited`: Too many requests: wait as long as `Retry-After` says","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}}},"500":{"description":"`server_error`: Something failed on Blupinta’s side: quote `errorId` when asking for help","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}}}}},"/v1/webhooks":{"post":{"operationId":"createWebhook","tags":["Webhooks"],"summary":"Make a webhook","description":"An HTTPS address of the app’s, on the public internet, that Blupinta posts each event of the projects the app reaches to (`V1WebhookBody`), signed: `Blupinta-Signature: t=<seconds>,v1=<hex>` is the HMAC-SHA256 of `<t>.<body>` with the webhook’s `secret`, given only now. Answer 2xx within 10 seconds; anything else is tried again after 1 min, 5 min, 30 min, 2 h and 12 h. Three days of nothing but failures turn it off.","security":[{"blupinta":["webhooks:manage"]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Any unique text of the app’s, the same for a retry: the request is acted on once, its answer kept a day","schema":{"type":"string","maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1WebhookInput"}}}},"responses":{"201":{"description":"Make a webhook","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1WebhookCreated"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"400":{"description":"`invalid_input`: What was sent isn’t right: `problems` says what, each with where","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"401":{"description":"`unauthenticated`: No valid access token for this API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"403":{"description":"`forbidden`: The token lacks the scope this operation needs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"429":{"description":"`rate_limited`: Too many requests: wait as long as `Retry-After` says","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}}},"500":{"description":"`server_error`: Something failed on Blupinta’s side: quote `errorId` when asking for help","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}}}},"get":{"operationId":"listWebhooks","tags":["Webhooks"],"summary":"The app’s webhooks","description":"Every webhook the app made, without their secrets; one turned off says when and why.","security":[{"blupinta":["webhooks:manage"]}],"responses":{"200":{"description":"The app’s webhooks","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1WebhookPage"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"401":{"description":"`unauthenticated`: No valid access token for this API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"403":{"description":"`forbidden`: The token lacks the scope this operation needs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"429":{"description":"`rate_limited`: Too many requests: wait as long as `Retry-After` says","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}}},"500":{"description":"`server_error`: Something failed on Blupinta’s side: quote `errorId` when asking for help","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}}}}},"/v1/webhooks/{webhookId}":{"delete":{"operationId":"deleteWebhook","tags":["Webhooks"],"summary":"Delete a webhook","description":"Nothing more is posted to it, deliveries on their way included.","security":[{"blupinta":["webhooks:manage"]}],"parameters":[{"name":"webhookId","in":"path","required":true,"description":"One of the app’s webhooks","schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Delete a webhook","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"401":{"description":"`unauthenticated`: No valid access token for this API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"403":{"description":"`forbidden`: The token lacks the scope this operation needs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"404":{"description":"`not_found`: Nothing there, or nothing this token may read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"429":{"description":"`rate_limited`: Too many requests: wait as long as `Retry-After` says","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}}},"500":{"description":"`server_error`: Something failed on Blupinta’s side: quote `errorId` when asking for help","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}}}}},"/v1/webhooks/{webhookId}/deliveries":{"get":{"operationId":"listDeliveries","tags":["Webhooks"],"summary":"A webhook’s deliveries","description":"Its latest 50 deliveries, newest first: what was posted, how many tries, what the app answered.","security":[{"blupinta":["webhooks:manage"]}],"parameters":[{"name":"webhookId","in":"path","required":true,"description":"One of the app’s webhooks","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"A webhook’s deliveries","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1DeliveryPage"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"401":{"description":"`unauthenticated`: No valid access token for this API","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"403":{"description":"`forbidden`: The token lacks the scope this operation needs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"404":{"description":"`not_found`: Nothing there, or nothing this token may read","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}},"429":{"description":"`rate_limited`: Too many requests: wait as long as `Retry-After` says","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}}},"500":{"description":"`server_error`: Something failed on Blupinta’s side: quote `errorId` when asking for help","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}}}}}}},"components":{"schemas":{"ApiIndex":{"type":"object","properties":{"name":{"type":"string"},"version":{"type":"string","const":"v1"},"resource":{"type":"string","description":"the resource an app's token must be for (`resource` when asking it)"},"issuer":{"type":"string","description":"where tokens come from: the OpenID provider"},"discovery":{"type":"string","description":"the provider's discovery document"},"scopes":{"type":"object","additionalProperties":{"type":"string"},"description":"the scopes `/v1` understands, each with what it gives"},"endpoints":{"type":"array","description":"every operation, as `METHOD /path`","items":{"type":"string"}},"openapi":{"type":"string","description":"the OpenAPI document describing every operation"},"docs":{"type":"string","description":"the guide's page for developers"}},"required":["name","version","resource","issuer","discovery","scopes","endpoints","openapi","docs"],"description":"`GET /v1`: what the public API is, for whoever builds an app on it"},"V1Comment":{"type":"object","properties":{"id":{"type":"string"},"authorName":{"type":["string","null"],"description":"who wrote it: their name, or their address when they gave none; null once their account is gone"},"body":{"type":"string"},"appName":{"type":["string","null"],"description":"the app that wrote it through `/v1`, as people read its name; null when written in the editor"},"editedAt":{"type":["string","null"],"description":"when it was last changed, ISO 8601; null when never"},"createdAt":{"type":"string"}},"required":["id","authorName","body","appName","editedAt","createdAt"],"description":"A message in a thread"},"V1CommentAnchor":{"type":"object","properties":{"ref":{"type":"object","properties":{"kind":{"type":"string"},"id":{"type":"string"}},"required":["kind","id"],"description":"the element, when it is on one: `{ kind: 'wall', id: 'north' }`"},"at":{"type":"array","minItems":2,"maxItems":2,"description":"the point, in the plan's sheet units","prefixItems":[{"type":"number","title":"x"},{"type":"number","title":"y"}]}},"required":["at"],"description":"Where a thread is pinned: on an element of a plan (by its kind and id), at a point of the plan's sheet"},"V1CommentInput":{"type":"object","properties":{"body":{"type":"string"}},"required":["body"],"description":"A message to add"},"V1Delivery":{"type":"object","properties":{"id":{"type":"string"},"event":{"$ref":"#/components/schemas/V1WebhookEvent"},"createdAt":{"type":"string"},"attempts":{"type":"integer"},"deliveredAt":{"type":["string","null"],"description":"when the app answered 2xx; null until then"},"lastStatus":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"the last try's HTTP status; null when no answer came"},"lastError":{"type":["string","null"]},"nextAttemptAt":{"type":["string","null"],"description":"when it is tried again; null once delivered or given up"}},"required":["id","event","createdAt","attempts","deliveredAt","lastStatus","lastError","nextAttemptAt"],"description":"One event posted, or on its way, to a webhook"},"V1WebhookEvent":{"type":"string","enum":["revision.saved","access.revoked"],"description":"What a webhook may be told"},"V1DeliveryPage":{"type":"object","properties":{"rows":{"type":"array","items":{"$ref":"#/components/schemas/V1Delivery"}},"next":{"type":["string","null"]}},"required":["rows","next"],"description":"A webhook's latest deliveries, newest first"},"V1Devices":{"type":"object","properties":{"devices":{"type":"array","items":{"$ref":"#/components/schemas/V1Device"}}},"required":["devices"],"description":"The devices an app declares for a project, replaced whole each time"},"V1Device":{"type":"object","properties":{"id":{"type":"string","description":"the app's own name for it: a Home Assistant entity_id (`light.soggiorno`)"},"name":{"type":"string","description":"as people read it"},"kind":{"type":"string","enum":["light","cover","switch","sensor","climate","media"],"description":"`cover`: shutters, curtains, awnings"},"room":{"type":["string","null"],"description":"the room it is in, as the app calls it; null when it doesn't say"}},"required":["id","name","kind","room"],"description":"A device an app knows, for the editor's binding pickers"},"V1File":{"type":"object","properties":{"sha256":{"type":"string","description":"its content's hash: how a home names it (a drawing's `src`, a floor's image)"},"contentType":{"type":"string","enum":["image/png","image/jpeg","image/webp","application/pdf"]},"size":{"type":"integer","description":"in bytes"},"name":{"type":"string","description":"its name, as uploaded"},"createdAt":{"type":"string","description":"when it was first uploaded, ISO 8601"}},"required":["sha256","contentType","size","name","createdAt"],"description":"A file of a project, as uploaded"},"V1ProjectInput":{"type":"object","properties":{"name":{"type":"string"},"address":{"type":["string","null"],"description":"where the home is; null or absent for none"}},"required":["name"],"description":"A project's name and where it is"},"V1SaveRevisionRequest":{"type":"object","properties":{"home":{"description":"the home, in any version of the format: brought to the current one as an import is"},"message":{"type":"string","description":"what changed, as people read it in the project's history"},"baseRevision":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"the last version the app had when it made this home; null when the project had none. Another one saved since: a `conflict`, so nobody's work is overwritten unseen"},"thumbnail":{"type":"string","description":"a picture of the home, WebP as base64, at most 256 kB; the version keeps none without it"}},"required":["home","message","baseRevision"],"description":"A version to save: a home, a line saying what changed, and the version it starts from"},"V1HomeCheck":{"type":"object","properties":{"readAs":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"the format's version the file says it is; null when it says none"},"checkedAt":{"type":"integer","description":"the version it was checked at: the current one, the file brought to it first as an import would"},"isValid":{"type":"boolean","description":"nothing in it keeps it from being saved as a version"},"problems":{"type":"array","items":{"$ref":"#/components/schemas/V1HomeProblem"}}},"required":["readAs","checkedAt","isValid","problems"],"description":"What the server says of a home file"},"V1HomeProblem":{"type":"object","properties":{"kind":{"type":"string","description":"`format`: the file isn't a home of the format (`message` says what, with its path); otherwise a check that blocks saving it as a version: `area-without-rooms`, `open-room`, `crossing`, `overlap`, `degenerate`, `shared-region`…"},"message":{"type":"string","description":"for `format`: what is wrong, with its path in the file"},"plan":{"type":"string","description":"the plan it concerns, by id, when it concerns one"},"detail":{"type":"object","additionalProperties":{},"description":"the problem as the checks found it: its walls, rooms, points, by the home's ids"}},"required":["kind"],"description":"One problem the server finds in a home"},"V1HomeCheckRequest":{"type":"object","properties":{"home":{"description":"the file's content, as JSON"}},"required":["home"],"description":"A home file to check: any version of the format"},"V1HostReferences":{"type":"object","properties":{"refs":{"type":"array","items":{"$ref":"#/components/schemas/V1HostReference"}}},"required":["refs"],"description":"The elements an app declares it needs: saving a version that drops one is refused, naming the app"},"V1HostReference":{"type":"object","properties":{"kind":{"$ref":"#/components/schemas/HostReferenceKind"},"id":{"type":"string"}},"required":["kind","id"],"description":"An element of the home an app's code names, by kind and id (the home format's own ids)"},"HostReferenceKind":{"type":"string","enum":["plan","level","area","presentation","room","wall","opening","item","light","awning","plate","stair"]},"V1Product":{"$ref":"#/components/schemas/ProductManifest","description":"A product of the catalog whole: its options, sizes, geometry, as a home holds it among its `products`"},"ProductManifest":{"type":"object","properties":{"id":{"type":"string","description":"namespace/name, stable forever (\"ikea/kallax\", \"jal/handmade-drawers\")"},"version":{"type":"number","description":"bumped on any change that can move or reshape an item"},"brand":{"type":"string"},"name":{"$ref":"#/components/schemas/Translated"},"description":{"$ref":"#/components/schemas/Translated"},"category":{"type":"string","description":"a path in the shared taxonomy: \"storage/shelving\", \"lighting/pendant\", \"decor/wall-art\""},"tags":{"type":"array","items":{"type":"string"}},"mount":{"$ref":"#/components/schemas/Mount"},"options":{"type":"array","items":{"$ref":"#/components/schemas/OptionSpec"}},"variants":{"type":"array","description":"option combinations the maker sells under its own article number (a slots option's as its slots' choices)","items":{"type":"object","properties":{"options":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/VariantValue"}},"article":{"type":"string"}},"required":["options","article"]}},"geometry":{"$ref":"#/components/schemas/GeometrySource"},"size":{"type":"object","properties":{"w":{"type":"string"},"h":{"type":"string"},"d":{"type":"string"}},"required":["w","h","d"],"description":"width (x), height (y), depth (z) as expressions of the options, m"},"clearances":{"type":"array","description":"the space it needs in use, beside its sides (furnishing.md §8): drawers pulled out, a door's swing, a chair","items":{"$ref":"#/components/schemas/Clearance"}},"bindings":{"type":"array","description":"what in it a house entity can drive","items":{"type":"object","properties":{"slot":{"type":"string"},"kind":{"type":"string","enum":["light","media","cover","appliance","sensor"]}},"required":["slot","kind"]}},"media":{"type":"object","properties":{"thumbnails":{"type":"array","items":{"type":"string"}}},"required":["thumbnails"]},"source":{"type":"string","description":"the product sheet or the measurement it comes from"},"links":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"kind":{"type":"string","enum":["product","manual","shop"]}},"required":["url","kind"]}}},"required":["id","version","name","category","tags","mount","options","geometry","size","source"]},"Translated":{"type":"object","properties":{"it":{"type":"string"},"en":{"type":"string"}},"required":["it","en"],"description":"App content, shared by every user: in each of the app's languages (furnishing.md F5)"},"Mount":{"type":"string","enum":["floor","wall","ceiling","surface","built-in"]},"OptionSpec":{"anyOf":[{"$ref":"#/components/schemas/ChoiceOption"},{"$ref":"#/components/schemas/MaterialOption"},{"$ref":"#/components/schemas/NumberOption"},{"$ref":"#/components/schemas/ToggleOption"},{"$ref":"#/components/schemas/ImageOption"},{"$ref":"#/components/schemas/SlotsOption"},{"$ref":"#/components/schemas/TextOption"}]},"ChoiceOption":{"type":"object","properties":{"kind":{"type":"string","const":"choice"},"choices":{"type":"array","description":"each with numbers the expressions read as `<option>.<name>` (KALLAX's `size.cols`)","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"$ref":"#/components/schemas/Translated"},"params":{"type":"object","additionalProperties":{"type":"number"}},"allowedIf":{"$ref":"#/components/schemas/AllowedIf"}},"required":["id","name"]}},"default":{"type":"string"},"id":{"type":"string"},"name":{"$ref":"#/components/schemas/Translated"},"visibleIf":{"$ref":"#/components/schemas/Condition"}},"required":["choices","default","id","kind","name"]},"AllowedIf":{"type":"array","description":"A rule between options (furnishing.md §3, a modular system): a value is offered only while every condition holds (BESTÅ's 180 frame only in white)","items":{"$ref":"#/components/schemas/Condition"}},"Condition":{"type":"object","properties":{"option":{"type":"string"},"in":{"type":"array","items":{"type":["string","number","boolean"]}}},"required":["option","in"],"description":"Shows (or allows) an option only when another holds one of some values"},"MaterialOption":{"type":"object","properties":{"kind":{"type":"string","const":"material"},"materials":{"type":"array","description":"the library's materials it is sold in","items":{"type":"string"}},"allowedIf":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/AllowedIf"},"description":"the materials sold only with some other values, by material"},"default":{"type":"string"},"id":{"type":"string"},"name":{"$ref":"#/components/schemas/Translated"},"visibleIf":{"$ref":"#/components/schemas/Condition"}},"required":["default","id","kind","materials","name"]},"NumberOption":{"type":"object","properties":{"kind":{"type":"string","const":"number"},"min":{"type":"number"},"max":{"type":"number"},"step":{"type":"number"},"unit":{"type":"string","enum":["m","count","degrees"]},"default":{"type":"number"},"id":{"type":"string"},"name":{"$ref":"#/components/schemas/Translated"},"visibleIf":{"$ref":"#/components/schemas/Condition"}},"required":["default","id","kind","max","min","name","step","unit"]},"ToggleOption":{"type":"object","properties":{"kind":{"type":"string","const":"toggle"},"default":{"type":"boolean"},"id":{"type":"string"},"name":{"$ref":"#/components/schemas/Translated"},"visibleIf":{"$ref":"#/components/schemas/Condition"}},"required":["default","id","kind","name"]},"ImageOption":{"type":"object","properties":{"kind":{"type":"string","const":"image"},"aspect":{"type":"number","description":"width over height of the picture's place"},"panels":{"type":"number","description":"a picture cut across panels (a multi-panel canvas), with the gap between them, m"},"gap":{"type":"number"},"id":{"type":"string"},"name":{"$ref":"#/components/schemas/Translated"},"visibleIf":{"$ref":"#/components/schemas/Condition"}},"required":["aspect","id","kind","name"]},"SlotsOption":{"type":"object","properties":{"kind":{"type":"string","const":"slots"},"count":{"type":"string","description":"how many slots, as an expression of the other options (KALLAX's cells)"},"choices":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"$ref":"#/components/schemas/Translated"},"allowedIf":{"$ref":"#/components/schemas/AllowedIf"}},"required":["id","name"]}},"id":{"type":"string"},"name":{"$ref":"#/components/schemas/Translated"},"visibleIf":{"$ref":"#/components/schemas/Condition"}},"required":["choices","count","id","kind","name"]},"TextOption":{"type":"object","properties":{"kind":{"type":"string","const":"text"},"maxLength":{"type":"number"},"default":{"type":"string"},"id":{"type":"string"},"name":{"$ref":"#/components/schemas/Translated"},"visibleIf":{"$ref":"#/components/schemas/Condition"}},"required":["default","id","kind","maxLength","name"]},"VariantValue":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"array","items":{"type":["string","null"]}}],"description":"A value a variant names for an option: a choice's or material's id, a number, yes or no, each slot's choice"},"GeometrySource":{"anyOf":[{"type":"object","properties":{"kind":{"type":"string","const":"builtin"},"generator":{"type":"string"}},"required":["kind","generator"]},{"type":"object","properties":{"kind":{"type":"string","const":"envelope"},"material":{"type":"string"}},"required":["kind"]},{"type":"object","properties":{"kind":{"type":"string","const":"gltf"},"models":{"type":"array","items":{"$ref":"#/components/schemas/ModelChoice"}},"materialVariant":{"type":"string"}},"required":["kind","models"]},{"type":"object","properties":{"kind":{"type":"string","const":"recipe"},"parts":{"type":"array","items":{"$ref":"#/components/schemas/RecipePart"}}},"required":["kind","parts"]}]},"ModelChoice":{"type":"object","properties":{"when":{"$ref":"#/components/schemas/When"},"model":{"$ref":"#/components/schemas/ModelRef"},"lod":{"$ref":"#/components/schemas/ModelRef"},"article":{"type":"string"},"perArticle":{"type":"number"}},"required":["model"],"description":"A model and the options that choose it; one without `when` is the fallback. A maker's component carries its article, and how many parts one article gives (STUBBARP's legs come two to a pack)"},"When":{"type":"object","additionalProperties":{"type":["string","number","boolean","null"]},"description":"Option values that must all hold: a choice's or a material's id, a number, yes or no; in a part made per slot, null for its slot left empty"},"ModelRef":{"type":"object","properties":{"sha256":{"type":"string"},"file":{"type":"string"}},"required":["sha256","file"],"description":"A model of the catalog: its bytes' sha256, and the file it is published from (`pnpm catalog:assets`)"},"RecipePart":{"anyOf":[{"type":"object","properties":{"shape":{"type":"string","enum":["box","cylinder"]},"size":{"$ref":"#/components/schemas/Triple"},"material":{"$ref":"#/components/schemas/MaterialRef"},"at":{"$ref":"#/components/schemas/Triple","description":"where the part's box starts: its left, bottom, back corner, from the product's origin (floor, middle, back)"},"count":{"type":"string","description":"how many, an expression; 0 leaves it out (an option switching a part off)"},"slots":{"type":"string","description":"one copy per slot of this slots option (`count` of them in each, then): in the part's `when` and its models', the option names this slot's own value; `index` names the slot's position"},"copy":{"type":"string","description":"in a part made per slot with a count, the name each copy's number within its slot goes by"},"when":{"$ref":"#/components/schemas/When","description":"the part is made only while these values hold"},"index":{"type":"string","description":"the name each copy's number (0, 1, …) goes by in the part's expressions"},"name":{"type":"string","description":"what the part is (\"drawer 1\"), for clearances and, later, animation"}},"required":["at","material","shape","size"]},{"type":"object","properties":{"shape":{"type":"string","const":"model"},"models":{"type":"array","items":{"$ref":"#/components/schemas/ModelChoice"}},"at":{"$ref":"#/components/schemas/Triple","description":"where the part's box starts: its left, bottom, back corner, from the product's origin (floor, middle, back)"},"count":{"type":"string","description":"how many, an expression; 0 leaves it out (an option switching a part off)"},"slots":{"type":"string","description":"one copy per slot of this slots option (`count` of them in each, then): in the part's `when` and its models', the option names this slot's own value; `index` names the slot's position"},"copy":{"type":"string","description":"in a part made per slot with a count, the name each copy's number within its slot goes by"},"when":{"$ref":"#/components/schemas/When","description":"the part is made only while these values hold"},"index":{"type":"string","description":"the name each copy's number (0, 1, …) goes by in the part's expressions"},"name":{"type":"string","description":"what the part is (\"drawer 1\"), for clearances and, later, animation"}},"required":["at","models","shape"]}]},"Triple":{"type":"array","minItems":3,"maxItems":3,"description":"Three expressions of the options, m: x, y, z or w, h, d","items":{"type":"string"}},"MaterialRef":{"anyOf":[{"type":"object","properties":{"option":{"type":"string"}},"required":["option"]},{"type":"object","properties":{"material":{"type":"string"}},"required":["material"]}],"description":"A part's finish: what a material option holds, or a material of the library"},"Clearance":{"type":"object","properties":{"use":{"$ref":"#/components/schemas/ClearanceUse"},"side":{"type":"string","enum":["front","back","left","right"],"description":"the side it opens out of, as the product faces: across its whole width on the front or back, its depth on a side"},"depth":{"type":"string","description":"how far out, m, as an expression of the options"},"source":{"type":"string","description":"where the measure comes from: the product sheet, a measurement, a usual allowance"}},"required":["use","side","depth","source"]},"ClearanceUse":{"type":"string","enum":["drawers","doors","seat","access","appliance"],"description":"What a clearance is kept free for, named in the editor's warnings"},"V1ProductPage":{"type":"object","properties":{"rows":{"type":"array","items":{"$ref":"#/components/schemas/V1ProductSummary"}},"total":{"type":"integer","description":"how many products the search found in all"},"next":{"type":["string","null"],"description":"where the next page starts, to send back as `after`; null on the last page"}},"required":["rows","total","next"],"description":"A page of a catalog search"},"V1ProductSummary":{"type":"object","properties":{"id":{"type":"string","description":"`namespace/name`, stable forever (\"ikea/kallax\"): read it whole at `/v1/catalog/products/{namespace}/{name}`"},"version":{"type":"integer","description":"bumped on any change that can move or reshape an item made of it"},"brand":{"type":["string","null"]},"name":{"type":"object","properties":{"it":{"type":"string"},"en":{"type":"string"}},"required":["it","en"]},"category":{"type":"string","description":"a path in the catalog's taxonomy (\"storage/shelving\")"},"mount":{"type":"string","enum":["floor","wall","ceiling","surface","built-in"]},"thumbnails":{"type":"array","description":"its thumbnails, by sha256: `/v1/catalog/thumbnails/{sha256}`","items":{"type":"string"}}},"required":["id","version","brand","name","category","mount","thumbnails"],"description":"A product of the catalog as a search lists it"},"V1Project":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string","description":"its name, as its people wrote it"},"address":{"type":["string","null"],"description":"where the home is, when they wrote it; null otherwise"},"lastRevision":{"anyOf":[{"type":"object","properties":{"number":{"type":"integer"},"createdAt":{"type":"string","description":"when it was saved, ISO 8601"}},"required":["number","createdAt"]},{"type":"null"}],"description":"the last version saved; null before the first"}},"required":["id","name","address","lastRevision"],"description":"A project an app may read"},"V1ProjectPage":{"type":"object","properties":{"rows":{"type":"array","items":{"$ref":"#/components/schemas/V1Project"}},"next":{"type":["string","null"],"description":"where the next page starts; null on the last page (today every project fits on one)"}},"required":["rows","next"],"description":"The projects an app may read, by name: one page of them"},"V1Revision":{"type":"object","properties":{"home":{"$ref":"https://blupinta.app/schema/home-6.json","description":"the home, in Blupinta's home format at the version its `schema` field names"},"files":{"type":"array","description":"the files of the project the home names (its drawings' images, a floor's photo), each read by its sha256","items":{"$ref":"#/components/schemas/V1File"}},"number":{"type":"integer","description":"the version's number, from 1, never reused","minimum":1},"message":{"type":"string","description":"what its author wrote when saving it"},"authorName":{"type":["string","null"],"description":"who saved it: their name, or their address when they gave none; null once their account is gone"},"restoredFrom":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"the version it brought back, when it was a restore; null otherwise","minimum":1},"createdAt":{"type":"string","description":"when it was saved, ISO 8601"}},"required":["authorName","createdAt","files","home","message","number","restoredFrom"],"description":"A saved version with its home"},"V1RevisionRow":{"type":"object","properties":{"number":{"type":"integer","description":"the version's number, from 1, never reused","minimum":1},"message":{"type":"string","description":"what its author wrote when saving it"},"authorName":{"type":["string","null"],"description":"who saved it: their name, or their address when they gave none; null once their account is gone"},"restoredFrom":{"anyOf":[{"type":"integer"},{"type":"null"}],"description":"the version it brought back, when it was a restore; null otherwise","minimum":1},"createdAt":{"type":"string","description":"when it was saved, ISO 8601"}},"required":["number","message","authorName","restoredFrom","createdAt"],"description":"A saved version of a project's home, without the home itself"},"V1RevisionPage":{"type":"object","properties":{"rows":{"type":"array","items":{"$ref":"#/components/schemas/V1RevisionRow"}},"next":{"type":["string","null"],"description":"where the next page starts; null on the last page (today every version fits on one)"}},"required":["rows","next"],"description":"The saved versions of a project, newest first: one page of them"},"V1RevisionSaved":{"type":"object","properties":{"number":{"type":"integer","description":"the new version's number: read it with `GET /v1/projects/{projectId}/home`"}},"required":["number"],"description":"The data of a `revision.saved` event: a version of the project was saved"},"V1Thread":{"type":"object","properties":{"id":{"type":"string"},"planId":{"type":"string","description":"the plan it is on, by the home's id"},"anchor":{"$ref":"#/components/schemas/V1CommentAnchor"},"resolvedAt":{"type":["string","null"],"description":"when it was marked resolved; null while open"},"createdAt":{"type":"string"},"comments":{"type":"array","description":"its messages, oldest first","items":{"$ref":"#/components/schemas/V1Comment"}}},"required":["id","planId","anchor","resolvedAt","createdAt","comments"],"description":"A conversation pinned on a plan"},"V1ThreadChange":{"type":"object","properties":{"isResolved":{"type":"boolean"}},"required":["isResolved"],"description":"A thread marked resolved, or open again"},"V1ThreadInput":{"type":"object","properties":{"planId":{"type":"string"},"anchor":{"$ref":"#/components/schemas/V1CommentAnchor"},"body":{"type":"string"}},"required":["planId","anchor","body"],"description":"A thread to open: where, and its first message"},"V1ThreadPage":{"type":"object","properties":{"rows":{"type":"array","items":{"$ref":"#/components/schemas/V1Thread"}},"next":{"type":["string","null"],"description":"where the next page starts; null on the last page"}},"required":["rows","next"]},"V1Webhook":{"type":"object","properties":{"id":{"type":"string"},"url":{"type":"string"},"events":{"type":"array","items":{"$ref":"#/components/schemas/V1WebhookEvent"}},"createdAt":{"type":"string"},"disabledAt":{"type":["string","null"],"description":"when it was turned off after three days of failing deliveries; null while on"},"disabledReason":{"type":["string","null"]}},"required":["id","url","events","createdAt","disabledAt","disabledReason"],"description":"Where an app is told what happens to the projects it reaches"},"V1WebhookBody":{"type":"object","properties":{"id":{"type":"string","description":"the delivery's id"},"type":{"$ref":"#/components/schemas/V1WebhookEvent"},"createdAt":{"type":"string","description":"when it happened, ISO 8601"},"data":{"type":"object","properties":{"projectId":{"type":"string"},"number":{"type":"integer","description":"for `revision.saved`: the new version's number"}},"required":["projectId"]}},"required":["id","type","createdAt","data"],"description":"The body posted to a webhook. Its headers: `Blupinta-Event`, `Blupinta-Delivery` (the same id on every retry) and `Blupinta-Signature: t=<seconds>,v1=<hex>`, the HMAC-SHA256 of `<t>.<body>` with the webhook's secret"},"V1WebhookCreated":{"type":"object","properties":{"secret":{"type":"string"},"id":{"type":"string"},"url":{"type":"string"},"events":{"type":"array","items":{"$ref":"#/components/schemas/V1WebhookEvent"}},"createdAt":{"type":"string"},"disabledAt":{"type":["string","null"],"description":"when it was turned off after three days of failing deliveries; null while on"},"disabledReason":{"type":["string","null"]}},"required":["createdAt","disabledAt","disabledReason","events","id","secret","url"],"description":"A webhook just made, with its secret: shown this once, to check each delivery's `Blupinta-Signature`"},"V1WebhookInput":{"type":"object","properties":{"url":{"type":"string","description":"an HTTPS address on the public internet"},"events":{"type":"array","items":{"$ref":"#/components/schemas/V1WebhookEvent"}}},"required":["url"],"description":"A webhook to make: where to post, and which events (all of them when none is said)"},"V1WebhookPage":{"type":"object","properties":{"rows":{"type":"array","items":{"$ref":"#/components/schemas/V1Webhook"}},"next":{"type":["string","null"],"description":"where the next page starts; null on the last page"}},"required":["rows","next"]},"V1Error":{"type":"object","properties":{"error":{"$ref":"#/components/schemas/V1ErrorCode"},"errorId":{"type":"string","description":"the server's id for a `server_error`, to quote when asking for help"},"problems":{"type":"array","description":"what is wrong, for `invalid_input`, `conflict`, `invalid_home`, `blocking_problems`","items":{"type":"string"}}},"required":["error"],"description":"The body of a refusal"},"V1ErrorCode":{"type":"string","enum":["unauthenticated","forbidden","not_found","rate_limited","server_error","invalid_input","conflict","invalid_home","blocking_problems","unsupported_file"],"description":"What `/v1` answers when it refuses"}},"headers":{"RateLimit-Limit":{"description":"The requests the caller may make in the window: an app counts per connection, every token together","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"The requests left in the window","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the window starts again","schema":{"type":"integer"}},"Retry-After":{"description":"Seconds to wait before asking again","schema":{"type":"integer"}},"Deprecation":{"description":"The operation is deprecated since this moment (RFC 9745, `@` and Unix seconds)","schema":{"type":"string","examples":["@1798761600"]}},"Sunset":{"description":"The day the operation stops answering, at least six months after its deprecation (RFC 8594)","schema":{"type":"string","examples":["Thu, 01 Jul 2027 00:00:00 GMT"]}}},"securitySchemes":{"blupinta":{"type":"oauth2","description":"Tokens from auth.blupinta.app for the resource `https://api.blupinta.app/v1`. Devices may also use the device flow (RFC 8628) at `/oidc/device/auth`.","flows":{"authorizationCode":{"authorizationUrl":"https://auth.blupinta.app/oidc/authorize","tokenUrl":"https://auth.blupinta.app/oidc/token","refreshUrl":"https://auth.blupinta.app/oidc/token","scopes":{"projects:read":"The saved versions of the projects the person chose","projects:write":"Saving new versions of those projects, uploading files to them, renaming them","projects:create":"Creating projects in the person’s account, which the app may then read and write","comments:read":"The comments on those projects: their threads and messages","comments:write":"Commenting on those projects as the person: opening threads, answering, resolving them","catalog:read":"Blupinta’s catalog of products, searched and read whole, with its models and thumbnails","webhooks:manage":"Being told, at an address of the app’s, what happens to the projects it reaches","hosts:manage":"Declaring which parts of those projects the app uses, and its devices to bind"}}}}}}}