{
  "openapi": "3.1.0",
  "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
  "info": {
    "title": "Správca nájmov API",
    "version": "1.0.0",
    "summary": "Signed server-to-server API for partner integrations: sign-up invitations with prefilled onboarding.",
    "description": "The Správca nájmov API lets a partner's server invite people to Správca nájmov and hand over what it already knows about them, so they do not type it twice.\n\nVersion 1 has three operations: test the connection, create an invitation and read an invitation. Machine-readable: [OpenAPI 3.1 JSON](/openapi.json) · [llms.txt](/llms.txt) for AI agents.\n\n# Overview\n\n1. Your server **creates an invitation** for one e-mail address and receives a `signupUrl`.\n2. **You deliver the link** to the person — in your own e-mail, app or chat. We send nothing at this point, and no account exists yet.\n3. The person opens the link, enters the invited address and **confirms it** from our e-mail. Only that address can use the link, and only once.\n4. They **set up their workspace**. If you sent `prefill`, the details are waiting there for them to review.\n5. Your server **reads the invitation** when you need its progress — for example when your user opens the screen that shows it. There are no webhooks and no need to poll on a timer.\n\n# Getting access\n\nWe create an API client for you and issue a key: a key id (`snk_…`) and a secret (`snsk_…`), shown once. Keep the secret on your server: never in a browser, a mobile app or a log.\n\n- A client can hold several keys, so rotation needs no downtime: ask for a new key, deploy it, then ask us to revoke the old one. A revoked key stops working at once.\n- We tie your client to your partner account and to the address your sign-up links use. A request cannot choose a partner, an organisation or a role.\n- Use `GET /v1/ping` to check a new key. It creates nothing and sends nothing.\n\n# Authentication\n\nEvery request under `/v1` is signed with HMAC-SHA256, following [RFC 9421 (HTTP Message Signatures)](https://www.rfc-editor.org/rfc/rfc9421.html), and carries an [RFC 9530](https://www.rfc-editor.org/rfc/rfc9530.html) `Content-Digest`. Use HTTPS only.\n\n## Headers\n\n| Header | Value |\n| --- | --- |\n| `Content-Digest` | `sha-256=:` + base64 of the SHA-256 of the exact body bytes + `:`. A request without a body (GET) sends the digest of the empty body: `sha-256=:47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=:` |\n| `Signature-Input` | `sig1=(\"@method\" \"@target-uri\" \"content-digest\");created=<unix seconds>;keyid=\"<key id>\";nonce=\"<random>\";alg=\"hmac-sha256\"` |\n| `Signature` | `sig1=:` + base64 of HMAC-SHA256(secret, signature base) + `:` |\n\n## Signature base\n\nFour lines joined by a line feed (`\\n`), with no line feed at the end:\n\n```text\n\"@method\": POST\n\"@target-uri\": https://api.spravcanajmov.sk/v1/invitations\n\"content-digest\": sha-256=:…:\n\"@signature-params\": (\"@method\" \"@target-uri\" \"content-digest\");created=…;keyid=\"…\";nonce=\"…\";alg=\"hmac-sha256\"\n```\n\n- `@method` is upper case.\n- `@target-uri` is the full URL you request: `https`, the host in lower case without a default port, then the path and query exactly as sent.\n- The last line repeats the `Signature-Input` value after `sig1=`, character for character.\n- The HMAC key is the secret exactly as issued: its UTF-8 bytes, `snsk_` prefix included.\n\n## Rules\n\n- `created` must be within 300 seconds of our clock. Keep your server's clock synchronised.\n- `nonce` is new for every request: 16–128 characters (letters, digits and `. _ ~ + / = -`). 32 hex characters from a secure random source are ideal. A nonce we have seen before is refused, so a retry must be signed again.\n- `alg` may be left out; when present it must be `\"hmac-sha256\"`. `expires` is optional.\n- You may cover more headers, for example `\"content-type\"`; the three above are required, in any order.\n- A `401` response carries `Accept-Signature: sig1=(\"@method\" \"@target-uri\" \"content-digest\");created;keyid;nonce;alg=\"hmac-sha256\"`, and its `code` says what was wrong.\n\n## Worked example\n\nCheck your implementation against this example. Every value is fixed and the result is computed by our server's own code.\n\n| Input | Value |\n| --- | --- |\n| Request | `POST https://api.spravcanajmov.sk/v1/invitations` |\n| Body | `{\"email\":\"jana.novakova@example.com\"}` |\n| keyid | `snk_0123456789abcdef0123456789abcdef` |\n| secret | `snsk_EXAMPLE-ONLY-NEVER-A-REAL-SECRET-0000000000` |\n| created | `1760529600` |\n| nonce | `8f2b6c1e4d7a9035b1c2d3e4f5a69788` |\n\nSignature base:\n\n```text\n\"@method\": POST\n\"@target-uri\": https://api.spravcanajmov.sk/v1/invitations\n\"content-digest\": sha-256=:vbvZbeoCAuUp7jMqsMm8dMuio7FNToiKxY+qDejaIDo=:\n\"@signature-params\": (\"@method\" \"@target-uri\" \"content-digest\");created=1760529600;keyid=\"snk_0123456789abcdef0123456789abcdef\";nonce=\"8f2b6c1e4d7a9035b1c2d3e4f5a69788\";alg=\"hmac-sha256\"\n```\n\nHeaders:\n\n```http\nContent-Digest: sha-256=:vbvZbeoCAuUp7jMqsMm8dMuio7FNToiKxY+qDejaIDo=:\nSignature-Input: sig1=(\"@method\" \"@target-uri\" \"content-digest\");created=1760529600;keyid=\"snk_0123456789abcdef0123456789abcdef\";nonce=\"8f2b6c1e4d7a9035b1c2d3e4f5a69788\";alg=\"hmac-sha256\"\nSignature: sig1=:z9hKHzMF3ylWMLYWxjR8Rr1yR1dqjYQ921rEchJzRuo=:\n```\n\n## Code\n\nNode.js 18+ (no dependencies):\n\n```javascript\nimport { createHash, createHmac, randomBytes, randomUUID } from \"node:crypto\";\n\nconst BASE_URL = \"https://api.spravcanajmov.sk\";\nconst KEY_ID = process.env.SPRAVCA_KEY_ID;   // snk_…\nconst SECRET = process.env.SPRAVCA_SECRET;   // snsk_… (server-side only)\n\nexport async function signedFetch(method, path, body, headers = {}) {\n  const url = new URL(path, BASE_URL).toString();\n  const bytes = body === undefined ? Buffer.alloc(0) : Buffer.from(JSON.stringify(body), \"utf8\");\n  const digest = `sha-256=:${createHash(\"sha256\").update(bytes).digest(\"base64\")}:`;\n  const created = Math.floor(Date.now() / 1000);\n  const nonce = randomBytes(16).toString(\"hex\");\n  const params = `(\"@method\" \"@target-uri\" \"content-digest\");created=${created};keyid=\"${KEY_ID}\";nonce=\"${nonce}\";alg=\"hmac-sha256\"`;\n  const base = [`\"@method\": ${method}`, `\"@target-uri\": ${url}`, `\"content-digest\": ${digest}`, `\"@signature-params\": ${params}`].join(\"\\n\");\n  const signature = createHmac(\"sha256\", SECRET).update(base, \"utf8\").digest(\"base64\");\n\n  const response = await fetch(url, {\n    method,\n    headers: {\n      ...headers,\n      ...(body === undefined ? {} : { \"Content-Type\": \"application/json\" }),\n      \"Content-Digest\": digest,\n      \"Signature-Input\": `sig1=${params}`,\n      Signature: `sig1=:${signature}:`\n    },\n    body: body === undefined ? undefined : bytes\n  });\n  return { status: response.status, body: await response.json() };\n}\n\n// Example: const invitation = await signedFetch(\"POST\", \"/v1/invitations\", { email: \"jana@example.com\" }, { \"Idempotency-Key\": randomUUID() });\n```\n\nPython 3:\n\n```python\nimport base64, hashlib, hmac, json, os, secrets, time, uuid\nimport requests  # pip install requests\n\nBASE_URL = \"https://api.spravcanajmov.sk\"\nKEY_ID = os.environ[\"SPRAVCA_KEY_ID\"]   # snk_…\nSECRET = os.environ[\"SPRAVCA_SECRET\"]   # snsk_… (server-side only)\n\n\ndef signed_request(method, path, body=None, headers=None):\n    url = BASE_URL + path\n    data = b\"\" if body is None else json.dumps(body, ensure_ascii=False).encode(\"utf-8\")\n    digest = \"sha-256=:\" + base64.b64encode(hashlib.sha256(data).digest()).decode() + \":\"\n    params = ('(\"@method\" \"@target-uri\" \"content-digest\")'\n              f';created={int(time.time())};keyid=\"{KEY_ID}\";nonce=\"{secrets.token_hex(16)}\";alg=\"hmac-sha256\"')\n    base = \"\\n\".join([f'\"@method\": {method}', f'\"@target-uri\": {url}',\n                      f'\"content-digest\": {digest}', f'\"@signature-params\": {params}'])\n    signature = base64.b64encode(hmac.new(SECRET.encode(\"utf-8\"), base.encode(\"utf-8\"), hashlib.sha256).digest()).decode()\n    all_headers = dict(headers or {}, **{\"Content-Digest\": digest, \"Signature-Input\": f\"sig1={params}\",\n                                          \"Signature\": f\"sig1=:{signature}:\"})\n    if body is not None:\n        all_headers[\"Content-Type\"] = \"application/json\"\n    response = requests.request(method, url, data=data if body is not None else None, headers=all_headers, timeout=30)\n    return response.status_code, response.json()\n\n# Example: signed_request(\"POST\", \"/v1/invitations\", {\"email\": \"jana@example.com\"}, {\"Idempotency-Key\": str(uuid.uuid4())})\n```\n\nPHP 8 (curl extension):\n\n```php\n<?php\nconst SPRAVCA_BASE_URL = 'https://api.spravcanajmov.sk';\n\nfunction spravca_request(string $method, string $path, ?array $body = null, array $headers = []): array\n{\n    $keyId = getenv('SPRAVCA_KEY_ID');   // snk_…\n    $secret = getenv('SPRAVCA_SECRET');  // snsk_… (server-side only)\n    $url = SPRAVCA_BASE_URL . $path;\n    $data = $body === null ? '' : json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR);\n    $digest = 'sha-256=:' . base64_encode(hash('sha256', $data, true)) . ':';\n    $params = sprintf('(\"@method\" \"@target-uri\" \"content-digest\");created=%d;keyid=\"%s\";nonce=\"%s\";alg=\"hmac-sha256\"',\n        time(), $keyId, bin2hex(random_bytes(16)));\n    $base = implode(\"\\n\", [\"\\\"@method\\\": $method\", \"\\\"@target-uri\\\": $url\",\n        \"\\\"content-digest\\\": $digest\", \"\\\"@signature-params\\\": $params\"]);\n    $signature = base64_encode(hash_hmac('sha256', $base, $secret, true));\n\n    $headers[] = \"Content-Digest: $digest\";\n    $headers[] = \"Signature-Input: sig1=$params\";\n    $headers[] = \"Signature: sig1=:$signature:\";\n    if ($body !== null) {\n        $headers[] = 'Content-Type: application/json';\n    }\n\n    $curl = curl_init($url);\n    curl_setopt_array($curl, [CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => $headers, CURLOPT_RETURNTRANSFER => true]);\n    if ($body !== null) {\n        curl_setopt($curl, CURLOPT_POSTFIELDS, $data);\n    }\n    $response = curl_exec($curl);\n    $status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);\n\n    return [$status, json_decode((string) $response, true)];\n}\n\n// Example: spravca_request('POST', '/v1/invitations', ['email' => 'jana@example.com'], ['Idempotency-Key: ' . bin2hex(random_bytes(16))]);\n```\n\nShell:\n\n```bash\n# A quick manual connection test (bash + openssl + curl). The secret is visible\n# to other local users in the process list here, so use the code above in production.\nKEY_ID=\"snk_…\"; SECRET=\"snsk_…\"\nURL=\"https://api.spravcanajmov.sk/v1/ping\"\nDIGEST=\"sha-256=:$(printf '' | openssl dgst -sha256 -binary | base64):\"\nPARAMS=\"(\\\"@method\\\" \\\"@target-uri\\\" \\\"content-digest\\\");created=$(date +%s);keyid=\\\"$KEY_ID\\\";nonce=\\\"$(openssl rand -hex 16)\\\";alg=\\\"hmac-sha256\\\"\"\nBASE=$(printf '\"@method\": GET\\n\"@target-uri\": %s\\n\"content-digest\": %s\\n\"@signature-params\": %s' \"$URL\" \"$DIGEST\" \"$PARAMS\")\nSIGNATURE=$(printf '%s' \"$BASE\" | openssl dgst -sha256 -hmac \"$SECRET\" -binary | base64)\ncurl -sS \"$URL\" -H \"Content-Digest: $DIGEST\" -H \"Signature-Input: sig1=$PARAMS\" -H \"Signature: sig1=:$SIGNATURE:\"\n```\n\n# Idempotency\n\n`POST /v1/invitations` requires an `Idempotency-Key` header: 1–255 visible ASCII characters, one per invitation you mean to create (a UUID is ideal). For 72 hours:\n\n- the same key with the same request returns the invitation it created the first time — `201` with `Idempotent-Replayed: true`;\n- the same key with a different request is refused with `422 idempotency_key_reused`;\n- concurrent requests with one key wait for each other, and all of them get the same invitation.\n\nAfter a timeout, a network error or a `5xx`, retry with the same key — and a new signature. Two invitations for one address are possible only with two keys.\n\n# Rate limits\n\nEach client has a per-minute request limit (60 unless agreed otherwise) and a limit on invitations per 24 hours (500 unless agreed otherwise). Authenticated responses carry `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset` (seconds); a `429` carries `Retry-After`. Repeated failed signatures from one address are throttled too.\n\n# Errors\n\nErrors are [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457.html) problem details (`application/problem+json`). Rely on `code`; `detail` is for people. Every response, including errors, carries `X-Request-Id` — quote it when you contact us.\n\n| code | HTTP | Meaning |\n| --- | --- | --- |\n| `invalid_request` | 400 | The body is not JSON (UTF-8), or a parameter is malformed. |\n| `idempotency_key_missing` | 400 | `POST /v1/invitations` needs an `Idempotency-Key` header: 1–255 visible ASCII characters, unique per invitation you mean to create. |\n| `signature_missing` | 401 | `Signature-Input` and `Signature` are missing. |\n| `signature_malformed` | 401 | The signature headers do not follow the profile: one signature, `created`, `keyid` and `nonce` parameters, a valid key id and nonce. |\n| `signature_components` | 401 | The signature must cover `\"@method\" \"@target-uri\" \"content-digest\"`, as plain quoted names, and every covered header must be present. |\n| `signature_algorithm` | 401 | Only `alg=\"hmac-sha256\"` is supported (or omit `alg`). |\n| `signature_expired` | 401 | `created` must be within 300 seconds of our clock, and `expires`, if sent, in the future. Check the clock of the calling server. |\n| `content_digest_missing` | 401 | Send `Content-Digest: sha-256=:…:` with every request, including an empty body. |\n| `content_digest_mismatch` | 401 | The digest does not match the body we received. Sign the exact bytes you send. |\n| `invalid_signature` | 401 | Wrong secret, unknown or revoked key, or a signature base that differs from ours — most often the `@target-uri` (scheme, host, path and query as sent) or the parameters line. |\n| `replayed_request` | 401 | This nonce was already used with this key. Generate a new nonce, and a new signature, for every request. |\n| `key_expired` | 401 | The key passed its expiry. Use a current key. |\n| `too_many_failed_attempts` | 429 | Too many unauthenticated requests from your address in the last minute. Fix the signing, then retry after `Retry-After` seconds. |\n| `client_disabled` | 403 | Your API client is disabled. Contact us. |\n| `insufficient_scope` | 403 | Your client does not have the scope this operation needs. |\n| `not_found` | 404 | No such resource for your client. Invitations of other clients are reported the same way. |\n| `method_not_allowed` | 405 | The resource does not support this HTTP method. |\n| `payload_too_large` | 413 | Request bodies are limited to 32 KiB. |\n| `unsupported_media_type` | 415 | Send `Content-Type: application/json`. |\n| `validation_failed` | 422 | The body does not match the schema. `errors` lists each field, including fields we do not support. |\n| `idempotency_key_reused` | 422 | This key was already used, with a different request, in the last 72 hours. Use a new key for a different invitation. |\n| `rate_limited` | 429 | Too many requests this minute. Retry after `Retry-After` seconds. |\n| `daily_limit_reached` | 429 | Your client created its daily maximum of invitations in the last 24 hours. |\n| `internal_error` | 500 | Something failed on our side. Retrying with the same `Idempotency-Key` is safe. |\n| `service_unavailable` | 503 | Temporarily unavailable. Retry later; retrying with the same `Idempotency-Key` is safe. |\n\n# Invitation lifecycle\n\n- An invitation is for **one e-mail address**, can be used **once**, and expires after 14 days unless you choose 1–30.\n- Creating one creates **no account and sends no e-mail**. You deliver `signupUrl`.\n- On the sign-up page, the person enters the invited address and receives our e-mail to confirm it and set a password. Someone who only has the forwarded link cannot use it for another address and never sees your prefill.\n- Someone who already has an account receives a sign-in e-mail instead. Until the person signs in, the invitation reads the same for a new and an existing account: the API never tells you whether an address is registered.\n- The workspace a person creates through your invitation belongs to your partner account.\n\n## Status\n\n| Field | Values |\n| --- | --- |\n| `status` | `active` · `used` (the invitee came through it and confirmed the address) · `expired` · `revoked` (withdrawn by us) |\n| `signup.status` | `not_started` · `requested` (asked for our e-mail) · `verified` (came back through our e-mail and signed in) |\n| `workspace.status` | `none` · `created` (after signing up through this invitation) · `existing` (had one already, nothing new created) |\n| `prefill.status` | `none` · `pending` · `deferred` · `applied` · `discarded` · `not_applied` (existing workspace, nothing imported) · `expired` |\n\nThe parts are independent: a sign-in is not a workspace, and a workspace does not mean your details were used. Until the person comes back through our e-mail, a new and an existing account read the same; `used`, `verified`, `existing` and `not_applied` appear only after that. A routine sign-in for any other reason does not count.\n\n# Prefill\n\nSend what you already know; every field is optional, and an incomplete property is fine — the form asks for anything missing.\n\n| Field | Fills in |\n| --- | --- |\n| `profile.fullName` | The suggested workspace name, when `workspace.name` is not given. |\n| `workspace.name`, `workspace.kind` | The workspace step of onboarding. |\n| `property.name`, `property.address.*`, `property.unit.*` | The first-property step: name, street, city, postal code, country (`SK`/`CZ`), district, unit label, floor and area. |\n\n- The details are shown **only to the invited person**, after they confirm the address and sign in. They can change anything, put the property off for later or discard it all. Nothing is created until they save, through the same checks as manual entry.\n- Someone who already has a workspace is never shown them: nothing is imported into an existing workspace (`prefill.status` becomes `not_applied`).\n- **Not accepted:** rent, deposit, energy costs, tenants, photos, descriptions or listing links. An advertised rent is not an agreed lease. Any field not in the schema is refused with `422 validation_failed`, naming it.\n\n# Data retention\n\n- Invitations and their status are kept, so a late lookup still answers.\n- Prefill details are deleted once applied or discarded, and 30 days after the link expires if nobody used them. Details a person put off are kept for up to 180 days. The outcome stays readable.\n- Nonces are kept for 15 minutes and idempotency keys for 72 hours.\n\n# Versioning\n\n`/v1` stays compatible: we may add response fields, optional request fields and new error codes, so ignore what you do not know. Breaking changes get a new version.\n\n**Changelog** — 2026-10-15: version 1 (ping, create and read invitations).",
    "x-server-version": "0.29.2"
  },
  "servers": [
    {
      "url": "https://api.spravcanajmov.sk"
    }
  ],
  "security": [
    {
      "RequestSignature": []
    }
  ],
  "tags": [
    {
      "name": "Connection",
      "description": "Check your credentials."
    },
    {
      "name": "Invitations",
      "description": "Invite a person to sign up, optionally with what you already know about their first property."
    }
  ],
  "paths": {
    "/v1/ping": {
      "get": {
        "operationId": "ping",
        "tags": [
          "Connection"
        ],
        "summary": "Test the connection",
        "description": "Verifies your signature and key and returns your client. Creates nothing and sends nothing; it counts towards the rate limit.",
        "parameters": [
          {
            "$ref": "#/components/parameters/ContentDigest"
          },
          {
            "$ref": "#/components/parameters/SignatureInput"
          }
        ],
        "responses": {
          "200": {
            "description": "Your credentials work.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ping"
                }
              }
            }
          },
          "401": {
            "description": "Signature missing, malformed, outside the time window, replayed or not verifying. See `code`.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "`client_disabled` or `insufficient_scope`.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`, `daily_limit_reached` or `too_many_failed_attempts`.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait."
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`. Retrying with the same Idempotency-Key is safe.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "`service_unavailable`.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "JavaScript",
            "label": "Node.js",
            "source": "const { status, body } = await signedFetch(\"GET\", \"/v1/ping\");"
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "status, body = signed_request(\"GET\", \"/v1/ping\")"
          },
          {
            "lang": "PHP",
            "label": "PHP",
            "source": "[$status, $body] = spravca_request('GET', '/v1/ping');"
          }
        ]
      }
    },
    "/v1/invitations": {
      "post": {
        "operationId": "createInvitation",
        "tags": [
          "Invitations"
        ],
        "summary": "Create an invitation",
        "description": "Creates a single-use sign-up link for one e-mail address, with optional prefill. No account is created and no e-mail is sent: deliver `signupUrl` yourself. Requires `Idempotency-Key`; see Idempotency.",
        "parameters": [
          {
            "$ref": "#/components/parameters/ContentDigest"
          },
          {
            "$ref": "#/components/parameters/SignatureInput"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateInvitationRequest"
              },
              "examples": {
                "minimal": {
                  "summary": "Just the address",
                  "value": {
                    "email": "jana.novakova@example.com"
                  }
                },
                "prefilled": {
                  "summary": "With what you know about the first property",
                  "value": {
                    "email": "jana.novakova@example.com",
                    "locale": "sk",
                    "expiresInDays": 14,
                    "reference": "rentodo-user-4821",
                    "prefill": {
                      "profile": {
                        "fullName": "Jana Nováková"
                      },
                      "workspace": {
                        "kind": "landlord"
                      },
                      "property": {
                        "name": "Byt Obchodná",
                        "address": {
                          "street": "Obchodná 12",
                          "city": "Bratislava",
                          "postalCode": "811 06",
                          "country": "SK",
                          "district": "Staré Mesto"
                        },
                        "unit": {
                          "label": "Byt 3",
                          "floor": "2",
                          "areaM2": 54.5
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created — or replayed for a repeated Idempotency-Key (see `Idempotent-Replayed`).",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Location": {
                "schema": {
                  "type": "string"
                },
                "description": "Path of the new invitation."
              },
              "Idempotent-Replayed": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "true"
                  ]
                },
                "description": "Present when this is the invitation an earlier request created."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invitation"
                },
                "example": {
                  "object": "invitation",
                  "id": "4b0f3d8e-6f7a-4d2c-9a51-2f8e1c3b7d90",
                  "email": "jana.novakova@example.com",
                  "reference": "rentodo-user-4821",
                  "locale": "sk",
                  "code": "7K4M-Q2XP",
                  "signupUrl": "https://moj.spravcanajmov.sk/signup?code=7K4M-Q2XP&lang=sk",
                  "createdAt": "2026-10-15T09:12:44.000Z",
                  "expiresAt": "2026-10-29T09:12:44.000Z",
                  "status": "active",
                  "signup": {
                    "status": "not_started",
                    "requestedAt": null
                  },
                  "workspace": {
                    "status": "none",
                    "createdAt": null
                  },
                  "prefill": {
                    "status": "pending",
                    "updatedAt": "2026-10-15T09:12:44.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request` or `idempotency_key_missing`.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Signature missing, malformed, outside the time window, replayed or not verifying. See `code`.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "`client_disabled` or `insufficient_scope`.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "`payload_too_large`.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "415": {
            "description": "`unsupported_media_type`.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "`validation_failed` (see `errors`) or `idempotency_key_reused`.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`, `daily_limit_reached` or `too_many_failed_attempts`.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait."
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`. Retrying with the same Idempotency-Key is safe.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "`service_unavailable`.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "JavaScript",
            "label": "Node.js",
            "source": "const { status, body } = await signedFetch(\n  \"POST\",\n  \"/v1/invitations\",\n  { email: \"jana.novakova@example.com\", locale: \"sk\", prefill: { property: { name: \"Byt Obchodná\", address: { city: \"Bratislava\", country: \"SK\" } } } },\n  { \"Idempotency-Key\": randomUUID() }\n);"
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "status, body = signed_request(\n    \"POST\", \"/v1/invitations\",\n    {\"email\": \"jana.novakova@example.com\", \"locale\": \"sk\",\n     \"prefill\": {\"property\": {\"name\": \"Byt Obchodná\", \"address\": {\"city\": \"Bratislava\", \"country\": \"SK\"}}}},\n    {\"Idempotency-Key\": str(uuid.uuid4())},\n)"
          },
          {
            "lang": "PHP",
            "label": "PHP",
            "source": "[$status, $body] = spravca_request('POST', '/v1/invitations', [\n    'email' => 'jana.novakova@example.com',\n    'locale' => 'sk',\n    'prefill' => ['property' => ['name' => 'Byt Obchodná', 'address' => ['city' => 'Bratislava', 'country' => 'SK']]],\n], ['Idempotency-Key: ' . bin2hex(random_bytes(16))]);"
          }
        ]
      }
    },
    "/v1/invitations/{invitationId}": {
      "get": {
        "operationId": "getInvitation",
        "tags": [
          "Invitations"
        ],
        "summary": "Read an invitation",
        "description": "Returns one of your invitations with its current status. Read it on demand, when you need to show progress; there is no need to poll.",
        "parameters": [
          {
            "$ref": "#/components/parameters/ContentDigest"
          },
          {
            "$ref": "#/components/parameters/SignatureInput"
          },
          {
            "name": "invitationId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "The `id` returned when you created it."
          }
        ],
        "responses": {
          "200": {
            "description": "The invitation.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Invitation"
                },
                "example": {
                  "object": "invitation",
                  "id": "4b0f3d8e-6f7a-4d2c-9a51-2f8e1c3b7d90",
                  "email": "jana.novakova@example.com",
                  "reference": "rentodo-user-4821",
                  "locale": "sk",
                  "code": "7K4M-Q2XP",
                  "signupUrl": "https://moj.spravcanajmov.sk/signup?code=7K4M-Q2XP&lang=sk",
                  "createdAt": "2026-10-15T09:12:44.000Z",
                  "expiresAt": "2026-10-29T09:12:44.000Z",
                  "status": "used",
                  "signup": {
                    "status": "verified",
                    "requestedAt": "2026-10-16T18:03:10.000Z"
                  },
                  "workspace": {
                    "status": "created",
                    "createdAt": "2026-10-16T18:09:55.000Z"
                  },
                  "prefill": {
                    "status": "applied",
                    "updatedAt": "2026-10-16T18:09:55.000Z"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Signature missing, malformed, outside the time window, replayed or not verifying. See `code`.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "`client_disabled` or `insufficient_scope`.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "`not_found` — also for an invitation of another client.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`, `daily_limit_reached` or `too_many_failed_attempts`.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait."
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "`internal_error`. Retrying with the same Idempotency-Key is safe.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "`service_unavailable`.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/RequestId"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "JavaScript",
            "label": "Node.js",
            "source": "const { status, body } = await signedFetch(\"GET\", `/v1/invitations/${invitationId}`);"
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "status, body = signed_request(\"GET\", f\"/v1/invitations/{invitation_id}\")"
          },
          {
            "lang": "PHP",
            "label": "PHP",
            "source": "[$status, $body] = spravca_request('GET', '/v1/invitations/' . $invitationId);"
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "RequestSignature": {
        "type": "apiKey",
        "in": "header",
        "name": "Signature",
        "description": "HMAC-SHA256 request signature (RFC 9421) with `Signature-Input` and `Content-Digest`. See Authentication."
      }
    },
    "parameters": {
      "ContentDigest": {
        "name": "Content-Digest",
        "in": "header",
        "required": true,
        "description": "RFC 9530 digest of the body, `sha-256=:<base64>:` (the empty body for GET).",
        "schema": {
          "type": "string"
        },
        "example": "sha-256=:47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=:"
      },
      "SignatureInput": {
        "name": "Signature-Input",
        "in": "header",
        "required": true,
        "description": "RFC 9421 signature parameters. See Authentication.",
        "schema": {
          "type": "string"
        },
        "example": "sig1=(\"@method\" \"@target-uri\" \"content-digest\");created=1760529600;keyid=\"snk_0123456789abcdef0123456789abcdef\";nonce=\"8f2b6c1e4d7a9035b1c2d3e4f5a69788\";alg=\"hmac-sha256\""
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": true,
        "description": "One per invitation you mean to create, 1–255 visible ASCII characters; a UUID is ideal. Kept for 72 hours.",
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 255
        },
        "example": "0b6f8f9c-0b1e-4a43-9b8e-5d4f7f3c2a10"
      }
    },
    "headers": {
      "RequestId": {
        "description": "Identifies the request in our logs.",
        "schema": {
          "type": "string"
        }
      },
      "RateLimitLimit": {
        "description": "Requests allowed in the current minute.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimitRemaining": {
        "description": "Requests left in the current minute.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimitReset": {
        "description": "Seconds until the current minute's window resets.",
        "schema": {
          "type": "integer"
        }
      }
    },
    "schemas": {
      "PrefillAddress": {
        "type": "object",
        "properties": {
          "street": {
            "type": "string",
            "minLength": 1,
            "maxLength": 180,
            "description": "Street and house number.",
            "examples": [
              "Obchodná 12"
            ]
          },
          "city": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "Town or city.",
            "examples": [
              "Bratislava"
            ]
          },
          "postalCode": {
            "type": "string",
            "minLength": 1,
            "maxLength": 20,
            "description": "Postal code as written locally.",
            "examples": [
              "811 06"
            ]
          },
          "country": {
            "type": "string",
            "enum": [
              "SK",
              "CZ"
            ],
            "description": "Country the property is in: `SK` or `CZ`. It decides the legal templates and the currency, so it is stated, never derived from the address."
          },
          "district": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "District or city part, when useful.",
            "examples": [
              "Staré Mesto"
            ]
          }
        },
        "additionalProperties": false
      },
      "PrefillUnit": {
        "type": "object",
        "properties": {
          "label": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80,
            "description": "How the landlord names the flat or unit.",
            "examples": [
              "Byt 3"
            ]
          },
          "floor": {
            "type": "string",
            "minLength": 1,
            "maxLength": 40,
            "description": "Floor, as written locally.",
            "examples": [
              "2"
            ]
          },
          "areaM2": {
            "type": "number",
            "exclusiveMinimum": 0,
            "maximum": 100000,
            "description": "Floor area in square metres.",
            "examples": [
              54.5
            ]
          }
        },
        "additionalProperties": false
      },
      "Prefill": {
        "type": "object",
        "properties": {
          "profile": {
            "type": "object",
            "properties": {
              "fullName": {
                "type": "string",
                "minLength": 1,
                "maxLength": 140,
                "description": "The invitee's full name. Used to suggest the workspace name.",
                "examples": [
                  "Jana Nováková"
                ]
              }
            },
            "additionalProperties": false,
            "description": "About the person."
          },
          "workspace": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 120,
                "description": "Name of the workspace (the landlord's or the company's).",
                "examples": [
                  "Prenájmy Nováková"
                ]
              },
              "kind": {
                "type": "string",
                "enum": [
                  "landlord",
                  "property_manager"
                ],
                "description": "`landlord` rents out own property; `property_manager` manages property for others."
              }
            },
            "additionalProperties": false,
            "description": "The workspace the invitee creates during onboarding."
          },
          "property": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 140,
                "description": "Name the landlord uses for the property.",
                "examples": [
                  "Byt Obchodná"
                ]
              },
              "address": {
                "$ref": "#/components/schemas/PrefillAddress"
              },
              "unit": {
                "$ref": "#/components/schemas/PrefillUnit"
              }
            },
            "additionalProperties": false,
            "description": "The first property. A partial property is fine: the form asks for anything missing."
          }
        },
        "additionalProperties": false
      },
      "CreateInvitationRequest": {
        "type": "object",
        "properties": {
          "email": {
            "type": "string",
            "maxLength": 254,
            "format": "email",
            "pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$",
            "description": "The invitee's e-mail address. The link works only for this address, and sign-up confirms it.",
            "examples": [
              "jana.novakova@example.com"
            ]
          },
          "locale": {
            "description": "Language of the sign-up page and of our e-mails to the invitee: `sk`, `cs` or `en`. Defaults to your client's language.",
            "type": "string",
            "enum": [
              "sk",
              "cs",
              "en"
            ]
          },
          "expiresInDays": {
            "description": "Days the link stays usable. Default 14, at most 30.",
            "examples": [
              14
            ],
            "type": "integer",
            "minimum": 1,
            "maximum": 30
          },
          "reference": {
            "description": "Your own identifier for this invitation, returned with it. Not shown to the invitee.",
            "examples": [
              "rentodo-user-4821"
            ],
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "prefill": {
            "$ref": "#/components/schemas/Prefill"
          }
        },
        "required": [
          "email"
        ],
        "additionalProperties": false
      },
      "Invitation": {
        "type": "object",
        "properties": {
          "object": {
            "type": "string",
            "const": "invitation"
          },
          "id": {
            "type": "string",
            "format": "uuid",
            "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
            "description": "Invitation ID. Use it to read the invitation's status."
          },
          "email": {
            "type": "string",
            "description": "The invited address, lower-cased.",
            "examples": [
              "jana.novakova@example.com"
            ]
          },
          "reference": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Your `reference`, if you sent one."
          },
          "locale": {
            "type": "string",
            "enum": [
              "sk",
              "cs",
              "en"
            ]
          },
          "code": {
            "type": "string",
            "description": "The invitation code, also contained in `signupUrl`.",
            "examples": [
              "7K4M-Q2XP"
            ]
          },
          "signupUrl": {
            "type": "string",
            "format": "uri",
            "description": "The link to send to the invitee. Opening it creates nothing: the invitee enters the invited address and confirms it from our e-mail.",
            "examples": [
              "https://moj.spravcanajmov.sk/signup?code=7K4M-Q2XP&lang=sk"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "used",
              "expired",
              "revoked"
            ],
            "description": "The link itself: `active` (usable), `used` (the invitee came through it and confirmed the address), `expired` (not used before it expired) or `revoked` (withdrawn by us)."
          },
          "signup": {
            "type": "object",
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "not_started",
                  "requested",
                  "verified"
                ],
                "description": "`not_started`; `requested` — the invitee asked for the sign-up e-mail; `verified` — they came back through our e-mail and signed in."
              },
              "requestedAt": {
                "anyOf": [
                  {
                    "type": "string",
                    "format": "date-time",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            },
            "required": [
              "status",
              "requestedAt"
            ],
            "additionalProperties": false,
            "description": "How far the invitee got with signing up."
          },
          "workspace": {
            "type": "object",
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "none",
                  "created",
                  "existing"
                ],
                "description": "`none`; `created` — they created their workspace after signing up through this invitation; `existing` — they already had one, so nothing new was created. Reported only once the invitee is `verified`."
              },
              "createdAt": {
                "anyOf": [
                  {
                    "type": "string",
                    "format": "date-time",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            },
            "required": [
              "status",
              "createdAt"
            ],
            "additionalProperties": false,
            "description": "The invitee's workspace."
          },
          "prefill": {
            "type": "object",
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "none",
                  "pending",
                  "deferred",
                  "applied",
                  "discarded",
                  "not_applied",
                  "expired"
                ],
                "description": "`none` — you sent no prefill; `pending` — not used yet; `deferred` — the invitee postponed the property for later; `applied` — the details were used to create their workspace or first property; `discarded` — the invitee discarded them; `not_applied` — they already had a workspace, so nothing was imported; `expired` — deleted unused at the end of the retention period."
              },
              "updatedAt": {
                "anyOf": [
                  {
                    "type": "string",
                    "format": "date-time",
                    "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            },
            "required": [
              "status",
              "updatedAt"
            ],
            "additionalProperties": false,
            "description": "What happened to the details you sent."
          }
        },
        "required": [
          "object",
          "id",
          "email",
          "reference",
          "locale",
          "code",
          "signupUrl",
          "createdAt",
          "expiresAt",
          "status",
          "signup",
          "workspace",
          "prefill"
        ],
        "additionalProperties": false
      },
      "Ping": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "client": {
            "type": "object",
            "properties": {
              "slug": {
                "type": "string"
              },
              "name": {
                "type": "string"
              }
            },
            "required": [
              "slug",
              "name"
            ],
            "additionalProperties": false
          },
          "keyId": {
            "type": "string",
            "examples": [
              "snk_0123456789abcdef0123456789abcdef"
            ]
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "examples": [
              [
                "invitations:create",
                "invitations:read"
              ]
            ]
          },
          "serverTime": {
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
          },
          "requestId": {
            "type": "string"
          }
        },
        "required": [
          "ok",
          "client",
          "keyId",
          "scopes",
          "serverTime",
          "requestId"
        ],
        "additionalProperties": false
      },
      "Problem": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "description": "A URI identifying the problem type; opening it leads to its documentation."
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991
          },
          "detail": {
            "type": "string"
          },
          "code": {
            "type": "string",
            "description": "Stable, machine-readable error code (see Errors)."
          },
          "requestId": {
            "type": "string",
            "description": "Quote it when you contact us."
          },
          "errors": {
            "description": "For `validation_failed`: each invalid field.",
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "path": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              },
              "required": [
                "path",
                "message"
              ],
              "additionalProperties": false
            }
          }
        },
        "required": [
          "type",
          "title",
          "status",
          "code",
          "requestId"
        ]
      }
    }
  }
}