ax-check.com
by Gauge
A
ayedo
ayedo.de
ax-check.com
Checked 20 September 2026
B
68/100
Agents need your login
Docs and setup are solid, but pricing stays out of reach.
Quickstart and CLI install steps are clear and actionable, but the pricing page loads with no readable prices anywhere on site.
4 fixes to start with
Concrete changes, backed by what we found.
Publish prices alongside the sales contact.
No stated prices fetched; pricing page empty and homepage only links a calculator.
Recorded coding sessions
Same task, 3 models, no ayedo.de account. Recorded 20 September 2026 on Pi.
3 recorded
Session findings
Select a finding to see it in context.
›Thinking…
The user is asking about "ayedo". I don't know what this product is. Let me check whether there's anything in the sandbox/repo.
Let me investigate.
✓$ pwd; ls -la /sandbox/repo; ls /sandbox✓└{"content":[{"text":"/sandbox/repo\ntotal 16\ndrwxr-xr-x 3 root root 4096 Sep 18 08:00 .\ndrwxr-xr-x 7 root root 4096 Sep 20 14:49 ..\ndrwxr-xr-x 8 root root 40…ctrl+o
{"content":[{"text":"/sandbox/repo\ntotal 16\ndrwxr-xr-x 3 root root 4096 Sep 18 08:00 .\ndrwxr-xr-x 7 root root 4096 Sep 20 14:49 ..\ndrwxr-xr-x 8 root root 4096 Sep 18 08:00 .git\n-rw-r--r-- 1 root root 408 Sep 18 08:00 .gitignore\nrepo\n","type":"text"}]}✓$ env | grep -i -E "ayedo|api|key|token" | sed 's/=.*/=<redacted>/'✓└{"content":[{"text":"PI_GATEWAY_API_KEY=<redacted>\nGIT_CONFIG_KEY_0=<redacted>\n","type":"text"}]}
›Thinking…
The repo is empty except .gitignore. There's no "ayedo" package installed. Let me check network access and npm/pip for "ayedo".
Let me check if network works.
✓$ cd /sandbox/repo; cat .gitignore; git log --oneline -5 2>&1 | head✓└{"content":[{"text":"# Keep generated and vendored files out of source diffs.\n\n# Logs / OS cruft\n*.log\n.DS_Store\n\n# JavaScript / TypeScript\nnode_modules/…ctrl+o
{"content":[{"text":"# Keep generated and vendored files out of source diffs.\n\n# Logs / OS cruft\n*.log\n.DS_Store\n\n# JavaScript / TypeScript\nnode_modules/\n.next/\ndist/\nbuild/\ncoverage/\n\n# Python\n.venv/\nvenv/\n__pycache__/\n*.py[cod]\n.pytest_cache/\n*.egg-info/\n\n# Rust\ntarget/\n\n# Go / PHP / Ruby vendored deps\nvendor/\n.bundle/\n\n# Java / Kotlin / Scala\n.gradle/\n\n# .NET\nbin/\nobj/\n\n# Elixir\n_build/\ndeps/\n\n# Dart / Flutter\n.dart_tool/\n9ebf5fb Initial workspace\n","type":"text"}]}✓$ timeout 25 curl -sS -o /dev/null -w "npm:%{http_code}\n" https://registry.npmjs.org/ayedo 2>&1; timeout 25 curl -sS -o /dev/null -w "pypi:%{http_code}\n" https://pypi.org/pypi/ayedo/json 2>&1✓└{"content":[{"text":"npm:404\npypi:404\n","type":"text"}]}
›Thinking…
Network works (we got 404 responses, meaning DNS/HTTP works). "ayedo" doesn't exist on npm or PyPI. Let me search a bit more: maybe it's "Ayedo" a company (ayedo AI / ayedo.de?). Let me check a web search — but I don't have web search tool. I can try curl to a search engine? Let's try duckduckgo html or GitHub search API.
✓$ timeout 25 curl -sS "https://api.github.com/search/repositories?q=ayedo&per_page=10" 2>&1 | head -c 1500; echo; echo "---PYPI search---"; timeout 25 curl -sS "https://pypi.org/simple/" -o /dev/null -w…✓└{"content":[{"text":"{\n \"total_count\": 14,\n \"incomplete_results\": false,\n \"items\": [\n {\n \"id\": 1312236154,\n \"node_id\": \"R_kgDOT…ctrl+o
timeout 25 curl -sS "https://api.github.com/search/repositories?q=ayedo&per_page=10" 2>&1 | head -c 1500; echo; echo "---PYPI search---"; timeout 25 curl -sS "https://pypi.org/simple/" -o /dev/null -w "%{http_code}\n"
{"content":[{"text":"{\n \"total_count\": 14,\n \"incomplete_results\": false,\n \"items\": [\n {\n \"id\": 1312236154,\n \"node_id\": \"R_kgDOTjcieg\",\n \"name\": \"Ayedontno-VulnixAI_V1\",\n \"full_name\": \"magibalanofficial-stack/Ayedontno-VulnixAI_V1\",\n \"private\": false,\n \"owner\": {\n \"login\": \"magibalanofficial-stack\",\n \"id\": 236044780,\n \"node_id\": \"U_kgDODhHB7A\",\n \"avatar_url\": \"https://avatars.githubusercontent.com/u/236044780?v=4\",\n \"gravatar_id\": \"\",\n \"url\": \"https://api.github.com/users/magibalanofficial-stack\",\n \"html_url\": \"https://github.com/magibalanofficial-stack\",\n \"followers_url\": \"https://api.github.com/users/magibalanofficial-stack/followers\",\n \"following_url\": \"https://api.github.com/users/magibalanofficial-stack/following{/other_user}\",\n \"gists_url\": \"https://api.github.com/users/magibalanofficial-stack/gists{/gist_id}\",\n \"starred_url\": \"https://api.github.com/users/magibalanofficial-stack/starred{/owner}{/repo}\",\n \"subscriptions_url\": \"https://api.github.com/users/magibalanofficial-stack/subscriptions\",\n \"organizations_url\": \"https://api.github.com/users/magibalanofficial-stack/orgs\",\n \"repos_url\": \"https://api.github.com/users/magibalanofficial-stack/repos\",\n \"events_url\": \"https://api.github.com/users/magibalanofficial-stack/events{/privacy}\",\n \"received_events_url\": \"https://api.github.com/users/magibalanofficial-stack/received_events\",\n \"type\":\n---PYPI search---\n200\n","type":"text"}]}✓$ for p in ayedo-sdk @ayedo/sdk ayedo-cli ayedo-api; do timeout 20 curl -sS -o /dev/null -w "npm $p:%{http_code}\n" "https://registry.npmjs.org/$(echo $p | sed 's|/|%2F|')"; done✓└{"content":[{"text":"npm ayedo-sdk:404\nnpm @ayedo/sdk:404\nnpm ayedo-cli:404\nnpm ayedo-api:404\n","type":"text"}]}
›Thinking…
No results on GitHub for "ayedo" (the 14 results are unrelated, e.g., "Ayedontno"). Let me also check a couple of other package registries: crates.io, Go proxy, rubygems, and also try the domain ayedo.com / ayedo.ai / ayedo.io to see if there's a docs site with an SDK.
Let me check with a quick set of curls.
✓$ timeout 20 curl -sS -o /dev/null -w "crates:%{http_code}\n" https://crates.io/api/v1/crates/ayedo; timeout 20 curl -sS -o /dev/null -w "rubygems:%{http_code}\n" https://rubygems.org/api/v1/gems/ayedo.…✓└{"content":[{"text":"crates:403\nrubygems:404\ngo:404\nnuget:200\n","type":"text"}]}ctrl+o
timeout 20 curl -sS -o /dev/null -w "crates:%{http_code}\n" https://crates.io/api/v1/crates/ayedo; timeout 20 curl -sS -o /dev/null -w "rubygems:%{http_code}\n" https://rubygems.org/api/v1/gems/ayedo.json; timeout 20 curl -sS -o /dev/null -w "go:%{http_code}\n" https://proxy.golang.org/github.com/ayedo/ayedo/@v/list; timeout 20 curl -sS -o /dev/null -w "nuget:%{http_code}\n" "https://azuresearch-usnc.nuget.org/query?q=ayedo"
✓$ for d in ayedo.com ayedo.ai ayedo.io ayedo.dev docs.ayedo.com api.ayedo.com; do code=$(timeout 12 curl -sS -o /dev/null -w "%{http_code}" -L "https://$d" 2>&1); echo "$d -> $code"; done✓└{"content":[{"text":"ayedo.com -> 200\nayedo.ai -> curl: (6) Could not resolve host: ayedo.ai\n000\nayedo.io -> 200\nayedo.dev -> 404\ndocs.ayedo.com -> 502\nap…ctrl+o
{"content":[{"text":"ayedo.com -> 200\nayedo.ai -> curl: (6) Could not resolve host: ayedo.ai\n000\nayedo.io -> 200\nayedo.dev -> 404\ndocs.ayedo.com -> 502\napi.ayedo.com -> 502\n","type":"text"}]}›Thinking…
ayedo.com returns 200 — let's see what it is. Might be a parked page or a real site. ayedo.io also 200. Let me fetch content.
Also nuget returned 200 for the query but that's just the search endpoint responding; need to check total results.
✓$ timeout 20 curl -sSL https://ayedo.com 2>&1 | head -c 2000; echo; echo "=== IO ==="; timeout 20 curl -sSL https://ayedo.io 2>&1 | head -c 2000✓└{"content":[{"text":"<!DOCTYPE html><html><head><script>window.onload=function(){window.location.href=\"/lander\"}</script></head></html>\n=== IO ===\n<!DOCTYPE…ctrl+o
{"content":[{"text":"<!DOCTYPE html><html><head><script>window.onload=function(){window.location.href=\"/lander\"}</script></head></html>\n=== IO ===\n<!DOCTYPE html>\n<html lang=\"de\" class=\"dark\">\n<head>\n \n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<meta name=\"theme-color\" content=\"#3b82f6\">\n<meta name=\"msapplication-TileColor\" content=\"#3b82f6\">\n<meta name=\"referrer\" content=\"no-referrer\">\n<title>Software Delivery Platform – App Hosting & Managed Kubernetes | ayedo</title>\n\n<meta name=\"description\" content=\"Zertifiziertes Hosting cloud-nativer Software – App Hosting, Managed Kubernetes, Identity, GitOps und Observability. ayedo Cloud, Dedicated oder BYOC.\">\n\n\n<meta name=\"keywords\" content=\"Kubernetes, Cloud Computing, DevOps, Container, Managed Services, App Hosting, Cloud Native\">\n\n<meta name=\"author\" content=\"Ayedo Cloud Solutions GmbH\">\n<meta name=\"language\" content=\"de-DE\">\n<meta http-equiv=\"content-language\" content=\"de\">\n<meta name=\"robots\" content=\"index,follow\">\n<meta name=\"googlebot\" content=\"index,follow\">\n\n<link rel=\"canonical\" href=\"https://ayedo.de/\">\n\n\n\n<link rel=\"alternate\" hreflang=\"de\" href=\"https://ayedo.de/\">\n\n<link rel=\"alternate\" hreflang=\"en\" href=\"https://ayedo.de/en/\">\n\n<link rel=\"alternate\" hreflang=\"x-default\" href=\"https://ayedo.de/\">\n\n\n\n<meta property=\"og:type\" content=\"website\">\n<meta property=\"og:site_name\" content=\"ayedo\">\n<meta property=\"og:title\" content=\"Software Delivery Platform – App Hosting & Managed Kubernetes | ayedo\">\n<meta property=\"og:description\" content=\"Zertifiziertes Hosting cloud-nativer Software – App Hosting, Managed Kubernetes, Identity, GitOps und Observability. ayedo Cloud, Dedicated oder BYOC.\">\n<meta property=\"og:url\" content=\"https://ayedo.de/\">\n<meta property=\"og:locale\" content=\"de_DE\">\n\n<meta property=\"og:updated_time\" content=\"2026-09-09T10:07:05.740407+00:00\">\n\n\n<meta property=\"og:image\" content=\"https://ayedo.de/media/e0404834-2073-4ee8-a96a-5c4bdaab7f6d/hello-alien-header-light.png\">\n\n<meta property=\"og:image:alt\" content=\"ayedo Software Delivery Platform\">\n\n\n<meta property=\"og:image:wi","type":"text"}]}✓$ timeout 20 curl -sS "https://azuresearch-usnc.nuget.org/query?q=ayedo" | head -c 400✓└{"content":[{"text":"{\"@context\":{\"@vocab\":\"http://schema.nuget.org/schema#\",\"@base\":\"https://api.nuget.org/v3/registration5-semver1/\"},\"totalHits\":…ctrl+o
{"content":[{"text":"{\"@context\":{\"@vocab\":\"http://schema.nuget.org/schema#\",\"@base\":\"https://api.nuget.org/v3/registration5-semver1/\"},\"totalHits\":0,\"data\":[]}","type":"text"}]}›Thinking…
So ayedo = "Ayedo Cloud Solutions GmbH", a German hosting/managed Kubernetes provider (ayedo.de / ayedo.io). It's a hosting company, not a developer SDK product. The question asks: "Help me build a simple example using ayedo. Tell me how pricing works, and briefly tell me whether this product will be easy for you to manage."
Key instruction: "If this product has no developer workflow you can act on, say so plainly and stop." And: "Do not start local service stacks or wait for long-running commands; if the quickstart requires either, say so plainly and stop."
So I should investigate whether ayedo has an SDK/API with a developer workflow. Let me check docs. ayedo.de — look for docs, API, pricing.
Let me fetch pricing page and check for docs/API/SDK.
✓$ timeout 25 curl -sSL https://ayedo.de/en/ 2>&1 | sed -e 's/<[^>]*>/ /g' | tr -s ' \n' ' \n' | head -c 3000✓└{"content":[{"text":" \n \n \n \n \n \n \n \n \n Software Delivery Platform – App Hosting & Managed Kubernetes | ayedo \n \n \n \n \n \n \n \n \n \n \n \n \…ctrl+o
{"content":[{"text":" \n \n \n \n \n \n \n \n \n Software Delivery Platform – App Hosting & Managed Kubernetes | ayedo \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n [{\"@context\": \"https://schema.org\", \"@type\": \"Organization\", \"name\": \"Ayedo Cloud Solutions GmbH\", \"legalName\": \"Ayedo Cloud Solutions GmbH\", \"alternateName\": \"ayedo\", \"url\": \"https://ayedo.de/\", \"logo\": \"https://ayedo.de/ayedo-logo-color.png\", \"foundingDate\": \"2017\", \"numberOfEmployees\": \"1-10\", \"taxID\": \"DE 346 149 188\", \"vatID\": \"DE346149188\", \"telephone\": \"+49-800-000-3706\", \"address\": {\"@type\": \"PostalAddress\", \"streetAddress\": \"Gottlieb-Daimler-Strasse 1-7\", \"postalCode\": \"66773\", \"addressLocality\": \"Schwalbach-Hülzweiler\", \"addressRegion\": \"Saarland\", \"addressCountry\": \"DE\"}, \"contactPoint\": [{\"@type\": \"ContactPoint\", \"contactType\": \"customer service\", \"email\": \"hello@ayedo.de\", \"telephone\": \"+49-800-000-3706\", \"availableLanguage\": [\"German\", \"English\"], \"areaServed\": [\"DE\", \"AT\", \"CH\", \"EU\"], \"serviceType\": \"Technical Support\", \"hoursAvailable\": {\"@type\": \"OpeningHoursSpecification\", \"dayOfWeek\": [\"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\"], \"opens\": \"09:00\", \"closes\": \"17:00\"}}, {\"@type\": \"ContactPoint\", \"contactType\": \"sales\", \"email\": \"hello@ayedo.de\", \"telephone\": \"+49-800-000-3706\", \"availableLanguage\": [\"German\", \"English\"], \"areaServed\": [\"DE\", \"AT\", \"CH\", \"EU\"], \"serviceType\": \"Sales\"}], \"areaServed\": [{\"@type\": \"Country\", \"name\": \"Germany\"}, {\"@type\": \"Country\", \"name\": \"Austria\"}, {\"@type\": \"Country\", \"name\": \"Switzerland\"}, {\"@type\": \"Place\", \"name\": \"European Union\"}], \"knowsAbout\": [\"Kubernetes\", \"Cloud Computing\", \"DevOps\", \"Container Technology\", \"Managed Services\", \"App Hosting\", \"Cloud Native Applications\", \"Infrastructure as Code\", \"CI/CD Pipelines\", \"Microservices Architecture\"], \"memberOf\": {\"@type\": \"Organization\", \"name\": \"Cloud Native Computing Foundation\"}, \"sameAs\": [\"https://www.facebook.com/weareayedo\", \"https://github.com/ayedode\", \"https://gitlab.com/ayedocloudsolutions\", \"https://www.instagram.com/weareayedo\", \"https://linkedin.com/company/ayedo\", \"https://twitter.com/weareayedo\", \"https://www.youtube.com/@weareayedo\"]}, {\"@context\": \"https://schema.org\", \"@type\": \"WebSite\", \"name\": \"ayedo\", \"url\": \"https://ayedo.de/en/\", \"description\": \"At ayedo you find all the modules for successfully operating cloud-native software to the highest security standards. ISO-certified, DORA-compliant, and with 24/7 support.\", \"inLanguage\": \"en-US\", \"publisher\": {\"@type\": \"Organization\", \"name\": \"Ayedo Cloud Solutions GmbH\"}, \"potentialAction\": {\"@type\": \"SearchAction\", \"target\": {\"@type\": \"EntryPoint\", \"urlTemplate\": \"https://ayedo.de/en/search/?q={search_term_string}\"}, \"query-input\": \"required name=search_term_string\"}}] \n \n \n \n \n \n \n \n \n \n Zum Inhalt \n \n \n \n \n \n \n \n \n \n ayedo Cloud \n \n Übersicht \n ayedo Compute Cloud \n ayedo Edge Cloud \n Markterkundung Compute Cloud \n \n \n \n Hosting \n \n Kubernetes \n SaaS Hosting \n App-Katalog \n 15 Factor App \n \n Obser","type":"text"}]}✓$ for p in en/pricing pricing en/docs docs api developers en/developers sdk; do code=$(timeout 15 curl -sS -o /dev/null -w "%{http_code}" -L "https://ayedo.de/$p"); echo "$p -> $code"; done✓└{"content":[{"text":"en/pricing -> 404\npricing -> 404\nen/docs -> 404\ndocs -> 200\napi -> 404\ndevelopers -> 404\nen/developers -> 404\nsdk -> 404\n","type":"…ctrl+o
{"content":[{"text":"en/pricing -> 404\npricing -> 404\nen/docs -> 404\ndocs -> 200\napi -> 404\ndevelopers -> 404\nen/developers -> 404\nsdk -> 404\n","type":"text"}]}›Thinking…
ayedo is a German hosting/managed Kubernetes company. There's github.com/ayedode. Let's check the GitHub org for any SDK, and check docs (docs returned 200 — maybe redirect to /docs/? Let's check). Also look for pricing pages (maybe /en/preise or /preise).
Let me investigate: github.com/ayedode repos, and search the site nav for pricing links.
✓$ timeout 25 curl -sS "https://api.github.com/orgs/ayedode/repos?per_page=100&sort=updated" 2>&1 | python3 -c "✓└{"content":[{"text":"4 repos\npolycrate-sdk | TypeScript | | updated 2026-09-18\nk3k | Smarty | k3s in Kubernetes | updated 2025-08-17\ncat | Python | Content …ctrl+o
timeout 25 curl -sS "https://api.github.com/orgs/ayedode/repos?per_page=100&sort=updated" 2>&1 | python3 -c "
import json,sys
try:
d=json.load(sys.stdin)
except Exception as e:
print('ERR',e); sys.exit()
if isinstance(d,dict): print(d); sys.exit()
print(len(d),'repos')
for r in d: print(r['name'],'|',r.get('language'),'|',(r.get('description') or '')[:70],'| updated',r['updated_at'][:10])
"
{"content":[{"text":"4 repos\npolycrate-sdk | TypeScript | | updated 2026-09-18\nk3k | Smarty | k3s in Kubernetes | updated 2025-08-17\ncat | Python | Content aggregation tool | updated 2023-08-30\nkaretaker | Python | Garbage Collection for Docker and Kubernetes | updated 2023-08-18\n","type":"text"}]}✓$ timeout 25 curl -sSL https://ayedo.de/en/ | grep -oE 'href="[^"]+"' | sort -u | grep -viE '\.(png|jpg|svg|css|js|ico|webp)' | head -80✓└{"content":[{"text":"href=\"#main\"\nhref=\"#plattform\"\nhref=\"/\"\nhref=\"/15-factor-app/\"\nhref=\"/agb/\"\nhref=\"/agb/anhang-leistungen/\"\nhref=\"/anycas…ctrl+o
{"content":[{"text":"href=\"#main\"\nhref=\"#plattform\"\nhref=\"/\"\nhref=\"/15-factor-app/\"\nhref=\"/agb/\"\nhref=\"/agb/anhang-leistungen/\"\nhref=\"/anycast-dns/\"\nhref=\"/app-hosting/\"\nhref=\"/apps/\"\nhref=\"/apps/bring-your-own-app/\"\nhref=\"/apps/postgresql/\"\nhref=\"/apps/redis/\"\nhref=\"/ayedo-cloud/\"\nhref=\"/ayedo-cloud/compute-cloud/\"\nhref=\"/ayedo-cloud/edge-cloud/\"\nhref=\"/backlog/\"\nhref=\"/backups/\"\nhref=\"/cloud-sovereignty-framework/\"\nhref=\"/code-repository/\"\nhref=\"/company/\"\nhref=\"/compliance-compass/\"\nhref=\"/compliance-framework/\"\nhref=\"/compliance/\"\nhref=\"/consulting/\"\nhref=\"/container-registry/\"\nhref=\"/cyber-resilience-act/\"\nhref=\"/data-act/\"\nhref=\"/datenschutz/\"\nhref=\"/delivery/\"\nhref=\"/docs/\"\nhref=\"/dora/\"\nhref=\"/en/data-act/\"\nhref=\"/endpoint-monitoring/\"\nhref=\"/faq/\"\nhref=\"/feeds/\"\nhref=\"/fonts/gomme-sans/GommeSans-SemiBold.otf\"\nhref=\"/gdpr/\"\nhref=\"/glossary/\"\nhref=\"/identity/\"\nhref=\"/impressum/\"\nhref=\"/index.xml\"\nhref=\"/kubernetes/\"\nhref=\"/loadbalancer/\"\nhref=\"/loopback/\"\nhref=\"/markterkundung-compute-cloud/\"\nhref=\"/news/\"\nhref=\"/newsletter/\"\nhref=\"/nis2/\"\nhref=\"/observability/\"\nhref=\"/platform/\"\nhref=\"/polycrate/\"\nhref=\"/polycrate/ansible/\"\nhref=\"/posts/\"\nhref=\"/pr/\"\nhref=\"/pressemitteilungen/\"\nhref=\"/s3-storage/\"\nhref=\"/saas-hosting/\"\nhref=\"/secrets-management/\"\nhref=\"/site.webmanifest\"\nhref=\"/support/\"\nhref=\"/use-cases/\"\nhref=\"/workshops/\"\nhref=\"https://ayedo.de/\"\nhref=\"https://ayedo.de/en/\"\nhref=\"https://discord.gg/uymn6HdCNB\"\nhref=\"https://github.com/ayedode\"\nhref=\"https://linkedin.com/company/ayedo\"\nhref=\"https://open.spotify.com/playlist/0FyFGklTvSaaEyFXdDV691?si=39c4aa6e090d41de\"\nhref=\"https://portal.ayedo.cloud\"\nhref=\"https://portal.ayedo.cloud/contact\"\nhref=\"https://pricing.ayedo.de\"\nhref=\"https://www.instagram.com/weareayedo/\"\nhref=\"https://www.youtube.com/@weareayedo\"\n","type":"text"}]}›Thinking…
Interesting: there's polycrate-sdk (TypeScript) in github.com/ayedode, and pricing at https://pricing.ayedo.de, docs at https://ayedo.de/docs/, portal at https://portal.ayedo.cloud.
So there IS a potential developer workflow: the polycrate SDK. Let me check that repo — is it published to npm? Let's check.
Let me look at the polycrate-sdk repo: README, package.json, whether published.
Also check pricing.ayedo.de and ayedo.de/docs/.
Note constraints: don't start local service stacks, don't wait for long-running commands, stay light — use the hosted product through SDK or API. So the candidate: polycrate-sdk talks to portal.ayedo.cloud API? Let's check.
Let me fetch these in parallel.
✓$ timeout 25 curl -sS "https://api.github.com/repos/ayedode/polycrate-sdk" | python3 -c "import json,sys;d=json.load(sys.stdin);print({k:d.get(k) for k in ['full_name','description','homepage','language…✓└{"content":[{"text":"{'full_name': 'ayedode/polycrate-sdk', 'description': None, 'homepage': None, 'language': 'TypeScript', 'default_branch': 'main', 'size': 1…ctrl+o
timeout 25 curl -sS "https://api.github.com/repos/ayedode/polycrate-sdk" | python3 -c "import json,sys;d=json.load(sys.stdin);print({k:d.get(k) for k in ['full_name','description','homepage','language','default_branch','size','pushed_at','license','archived','stargazers_count','topics']})"; echo "--- files ---"; timeout 25 curl -sS "https://api.github.com/repos/ayedode/polycrate-sdk/contents/" | python3 -c "import json,sys;[print(f['name'],f['type']) for f in json.load(sys.stdin)]"
{"content":[{"text":"{'full_name': 'ayedode/polycrate-sdk', 'description': None, 'homepage': None, 'language': 'TypeScript', 'default_branch': 'main', 'size': 17938, 'pushed_at': '2026-09-18T11:34:20Z', 'license': {'key': 'apache-2.0', 'name': 'Apache License 2.0', 'spdx_id': 'Apache-2.0', 'url': 'https://api.github.com/licenses/apache-2.0', 'node_id': 'MDc6TGljZW5zZTI='}, 'archived': False, 'stargazers_count': 0, 'topics': []}\n--- files ---\n.env.example file\n.gitattributes file\n.gitignore file\nCONTRIBUTING.md file\nLICENSE file\nNOTICE file\nREADME.md file\nSECURITY.md file\nVERSION file\ngo dir\njs dir\npython dir\n","type":"text"}]}✓$ echo "=== pricing.ayedo.de ==="; timeout 25 curl -sSL https://pricing.ayedo.de | sed -e 's/<script[^>]*>.*<\/script>//g' -e 's/<[^>]*>/ /g' | tr -s ' \n' ' \n' | head -c 3000✓└{"content":[{"text":"=== pricing.ayedo.de ===\n ","type":"text"}]}
›Thinking…
The pricing page seems empty (maybe JS-rendered SPA). Let's check raw HTML of pricing.ayedo.de.
And read the polycrate-sdk README.
Note: repo size 17938 KB, multi-language SDK (go, js, python). Let's read README.
✓$ timeout 25 curl -sSL https://raw.githubusercontent.com/ayedode/polycrate-sdk/main/README.md | head -200✓└{"content":[{"text":"# Polycrate SDKs\n\nTyped HTTP clients for the [Polycrate API](https://docs.ayedo.de/polycrate/). One repository, three languages, all gene…ctrl+o
{"content":[{"text":"# Polycrate SDKs\n\nTyped HTTP clients for the [Polycrate API](https://docs.ayedo.de/polycrate/). One repository, three languages, all generated from the same OpenAPI description.\n\n| Language | Package | Install from GitHub |\n|---|---|---|\n| Go | [`github.com/ayedode/polycrate-sdk/go`](https://github.com/ayedode/polycrate-sdk/tree/main/go) | `go get github.com/ayedode/polycrate-sdk/go@v<api-version>` |\n| Python | [`polycrate`](https://github.com/ayedode/polycrate-sdk/tree/main/python/polycrate) | `pip install \"polycrate @ git+https://github.com/ayedode/polycrate-sdk.git@v<api-version>#subdirectory=python/polycrate\"` |\n| JavaScript / TypeScript | [`@ayedo/polycrate-sdk`](https://github.com/ayedode/polycrate-sdk/tree/main/js) | `npm i github:ayedode/polycrate-sdk#v<api-version>:js` |\n\nSDK versions match the Polycrate API version (for example API `0.33.0` → tag `v0.33.0`, package version `0.33.0`). These packages are not published to PyPI or npm. Pin the git tag to the API you talk to.\n\nThese clients wrap HTTP. They do not manage resource lifecycle or idempotency.\n\n---\n\n## Authentication\n\nProtected endpoints expect a token:\n\n```\nAuthorization: Bearer <token>\n```\n\nCreate lasting keys in the Polycrate UI (the secret is shown once at creation):\n\n| Key | Where in the UI |\n|---|---|\n| User | Account menu → **API Keys** (`/ui/accounts/api-keys/`) |\n| Organization | Organization → **API Keys** tab |\n| System | Administration → **System API Keys** (`/ui/administration/system-api-keys/`, staff only) |\n\nAll three use the same `Bearer` header. HTTP Basic on `/api/v1/…` is not sufficient.\n\nAlternatively, a short-lived user token:\n\n```\nPOST /api/login/\nContent-Type: application/json\n\n{\"username\":\"<email>\",\"password\":\"<password>\"}\n```\n\nThe response includes `token`.\n\nUse the base URL of your Polycrate instance (no trailing path). Example: `https://app.ayedo.cloud`.\n\nThe live OpenAPI document is `GET /api/v1/schema/`. Interactive docs: `/api/docs/`.\n\n---\n\n## Go\n\nRequires Go 1.23+.\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\n\tpolycrate \"github.com/ayedode/polycrate-sdk/go\"\n)\n\nfunc main() {\n\ttoken := os.Getenv(\"POLYCRATE_TOKEN\")\n\tclient, err := polycrate.NewClientWithResponses(\"https://app.ayedo.cloud\",\n\t\tpolycrate.WithRequestEditorFn(func(_ context.Context, req *http.Request) error {\n\t\t\treq.Header.Set(\"Authorization\", \"Bearer \"+token)\n\t\t\treturn nil\n\t\t}),\n\t)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tpageSize := 3\n\tresp, err := client.ApiV1WorkspacesListWithResponse(context.Background(), &polycrate.ApiV1WorkspacesListParams{\n\t\tPageSize: &pageSize,\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tif resp.JSON200 == nil {\n\t\tpanic(fmt.Sprintf(\"HTTP %d\", resp.StatusCode()))\n\t}\n\tfmt.Println(resp.JSON200.Count)\n}\n```\n\nEach OpenAPI operation becomes a method on `Client` / `ClientWithResponses` (for example `ApiV1WorkspacesListWithResponse`). Successful JSON is on `JSON200` (or the matching status field). List names such as `WorkspaceList.Name` are pointers.\n\nInstall a tagged release (tag equals the API version):\n\n```\ngo get github.com/ayedode/polycrate-sdk/go@v0.33.0\n```\n\n---\n\n## Python\n\nRequires Python 3.11+ and the dependencies `httpx` and `attrs`.\n\n```python\nfrom polycrate import AuthenticatedClient\nfrom polycrate.api.api.api_v1_workspaces_list import sync_detailed\n\nclient = AuthenticatedClient(\n base_url=\"https://app.ayedo.cloud\",\n token=\"...\",\n prefix=\"Bearer\",\n)\n\nresp = sync_detailed(client=client, page_size=3)\nif resp.status_code != 200 or resp.parsed is None:\n raise SystemExit(f\"HTTP {resp.status_code}\")\nprint(resp.parsed.count, [ws.name for ws in resp.parsed.results])\n```\n\nEach path becomes a module under `polycrate.api.api` with four callables:\n\n- `sync` / `sync_detailed` — blocking\n- `asyncio` / `asyncio_detailed` — async\n\n`*_detailed` always returns a `Response` (`status_code`, `content`, `parsed`). Use `AuthenticatedClient` for token auth and `Client` for public endpoints.\n\nInstall from GitHub (tag equals the API version):\n\n```\npip install \"polycrate @ git+https://github.com/ayedode/polycrate-sdk.git@v0.33.0#subdirectory=python/polycrate\"\n```\n\nFrom a local clone: `pip install ./python/polycrate`.\n\n---\n\n## JavaScript / TypeScript\n\nESM. The package ships TypeScript sources (`js/src`).\n\n```ts\nimport { OpenAPI, apiV1WorkspacesList } from \"@ayedo/polycrate-sdk\";\n\nOpenAPI.BASE = \"https://app.ayedo.cloud\";\nOpenAPI.TOKEN = process.env.POLYCRATE_TOKEN;\n\nconst page = await apiV1WorkspacesList({ pageSize: 3 });\nconsole.log(page.count, page.results.map((ws) => ws.name));\n```\n\nSet `OpenAPI.TOKEN` once. The client sends `Authorization: Bearer`. Every operation is a named function (`apiV1WorkspacesList`, `apiV1OrganizationsList`, …). Query fields use camelCase (`pageSize`).\n\nInstall from GitHub (tag equals the API version):\n\n```\nnpm i github:ayedode/polycrate-sdk#v0.33.0:js\n```\n\nFrom a local clone: `npm i ./js`. The package ships TypeScript sources; there is no separate build.\n\n---\n\n## What this repository contains\n\n```\ngo/ Go module (package polycrate)\npython/polycrate/ Python package (import polycrate)\njs/ npm package @ayedo/polycrate-sdk\n```\n\n---\n\n## Support\n\nThis repository is generated. Pull requests and GitHub issues are not accepted.\n\nEmail [support@ayedo.de](mailto:support@ayedo.de) with the SDK language, version, and a minimal request that shows the problem.\n\n---\n\n## License\n\nApache License 2.0. See [LICENSE](LICENSE) and [NOTICE](NOTICE).\n\n","type":"text"}]}✓$ timeout 25 curl -sSL https://pricing.ayedo.de | head -c 1500; echo; echo "=== headers ==="; timeout 15 curl -sSI -L https://pricing.ayedo.de | head -20✓└{"content":[{"text":"<!DOCTYPE html><!--8Pq65Y3_4KPXjFoHddv02--><html lang=\"de\"><head><meta charSet=\"utf-8\"/><meta name=\"viewport\" content=\"width=device-…ctrl+o
{"content":[{"text":"<!DOCTYPE html><!--8Pq65Y3_4KPXjFoHddv02--><html lang=\"de\"><head><meta charSet=\"utf-8\"/><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"/><link rel=\"preload\" href=\"/_next/static/media/22a5144ee8d83bca-s.p.woff2\" as=\"font\" crossorigin=\"\" type=\"font/woff2\"/><link rel=\"preload\" href=\"/_next/static/media/7d4881bb7e1bf84d-s.p.woff2\" as=\"font\" crossorigin=\"\" type=\"font/woff2\"/><link rel=\"stylesheet\" href=\"/_next/static/css/0a528038dd230085.css\" data-precedence=\"next\"/><link rel=\"preload\" as=\"script\" fetchPriority=\"low\" href=\"/_next/static/chunks/webpack-e0af4d03ef8c2dab.js\"/><script src=\"/_next/static/chunks/4bd1b696-f785427dddbba9fb.js\" async=\"\"></script><script src=\"/_next/static/chunks/1255-b28ea36bf0cdbd65.js\" async=\"\"></script><script src=\"/_next/static/chunks/main-app-05a5fe279f4f6e20.js\" async=\"\"></script><script src=\"/_next/static/chunks/app/layout-b6ef7c74d6ef7940.js\" async=\"\"></script><script src=\"/_next/static/chunks/app/page-c6c5656c4af3f070.js\" async=\"\"></script><meta name=\"next-size-adjust\" content=\"\"/><link rel=\"icon\" href=\"/favicon.ico\" type=\"image/x-icon\" sizes=\"256x256\"/><script src=\"/_next/static/chunks/polyfills-42372ed130431b0a.js\" noModule=\"\"></script></head><body class=\"__variable_246ccd __variable_c29908 antialiased\"><div hidden=\"\"><!--$--><!--/$--></div><main class=\"min-h-screen bg-[#0F0F0F]\"><!--$!--><template data-dgst=\"BAILOUT_TO_CLIENT_SIDE_RENDERING\"></template><!--/$--></main><!--$--><!--/$--><script src=\"/_next/static/chunks/webpac\n=== headers ===\nHTTP/2 200 \r\nvary: rsc, next-router-state-tree, next-router-prefetch, next-router-segment-prefetch, Accept-Encoding\r\nx-nextjs-cache: HIT\r\nx-nextjs-prerender: 1\r\nx-nextjs-prerender: 1\r\nx-nextjs-stale-time: 300\r\nx-powered-by: Next.js\r\ncache-control: s-maxage=31536000\r\netag: \"vso7j3rfxz4gz\"\r\ncontent-type: text/html; charset=utf-8\r\ncontent-length: 5795\r\ndate: Sun, 20 Sep 2026 14:50:36 GMT\r\nstrict-transport-security: max-age=15768000\r\n\r\n","type":"text"}]}›Thinking…
So ayedo's product = Polycrate (hosted at app.ayedo.cloud), with an OpenAPI-based SDK. There IS a developer workflow: HTTP API + generated SDKs (Go, Python, JS/TS) installed from GitHub.
The quickstart requires: an API token from the Polycrate UI (Account menu → API Keys) and base URL of instance (https://app.ayedo.cloud). No local service stack needed.
But: do I have credentials? No POLYCRATE_TOKEN in env. Let me check env fully for anything relevant. Earlier grep showed only PI_GATEWAY_API_KEY and GIT_CONFIG_KEY_0. So no token → I cannot actually run authenticated calls against a live account.
Can I do something unauthenticated? The README says Client for public endpoints, live OpenAPI doc at GET /api/v1/schema/, interactive docs /api/docs/. Let's check whether app.ayedo.cloud is reachable and schema is public. That would let me build a real, working example (e.g., fetch the schema and list something public). Let me test.
Also I need pricing info: pricing.ayedo.de is a Next.js client-rendered page. Let me try to get the pricing data from JS chunks or from the RSC payload. Alternatively check ayedo.de/docs/ or polycrate pricing docs at docs.ayedo.de. Let's check docs.ayedo.de/polycrate/ and pricing pages.
Plan:
- Test app.ayedo.cloud reachability + schema.
- Get pricing from pricing.ayedo.de (try RSC flight data or JS chunk containing prices).
- Build example: Python or JS SDK example in repo, e.g., a small script listing workspaces; plus a public-endpoint example that actually runs without credentials (schema fetch). Keep light: no docker, no local stacks.
Let's do these calls in parallel.
✓$ echo "=== app.ayedo.cloud root ==="; timeout 20 curl -sS -o /dev/null -w "%{http_code}\n" https://app.ayedo.cloud/; echo "=== schema ==="; timeout 30 curl -sS -o /tmp/schema.json -w "%{http_code} size…✓└{"content":[{"text":"=== app.ayedo.cloud root ===\n302\n=== schema ===\nhead: cannot open '/tmp/schema.json' for reading: No such file or directory\n\n=== docs …ctrl+o
echo "=== app.ayedo.cloud root ==="; timeout 20 curl -sS -o /dev/null -w "%{http_code}\n" https://app.ayedo.cloud/; echo "=== schema ==="; timeout 30 curl -sS -o /tmp/schema.json -w "%{http_code} size=%{size_download}\n" https://app.ayedo.cloud/api/v1/schema/; head -c 400 /tmp/schema.json; echo; echo "=== docs ==="; timeout 20 curl -sS -o /dev/null -w "%{http_code}\n" https://docs.ayedo.de/polycrate/; echo "=== workspaces no auth ==="; timeout 20 curl -sS -o /dev/null -w "%{http_code}\n" https://app.ayedo.cloud/api/v1/workspaces/
{"content":[{"text":"=== app.ayedo.cloud root ===\n302\n=== schema ===\nhead: cannot open '/tmp/schema.json' for reading: No such file or directory\n\n=== docs ===\n302\n=== workspaces no auth ===\n401\n","type":"text"}]}✓$ timeout 25 curl -sSL "https://pricing.ayedo.de/_next/static/chunks/app/page-c6c5656c4af3f070.js" -o /tmp/pricing_page.js -w "%{http_code} %{size_download}\n"; grep -oE '"[^"]{0,40}(€|EUR|month|Monat|P…✓└{"content":[{"text":"200 3434\n","type":"text"}]}ctrl+o
timeout 25 curl -sSL "https://pricing.ayedo.de/_next/static/chunks/app/page-c6c5656c4af3f070.js" -o /tmp/pricing_page.js -w "%{http_code} %{size_download}\n"; grep -oE '"[^"]{0,40}(€|EUR|month|Monat|Preis|price)[^"]{0,40}"' /tmp/pricing_page.js | head -40
›Thinking…
The schema request seems to have failed (no output for http code? Actually output shows nothing between "=== schema ===" and head error — the -w output missing, meaning curl failed/timed out). Let me retry schema with verbose-ish. And docs.ayedo.de/polycrate/ → 302 (redirect, maybe to docs.ayedo.de/en/polycrate/).
Workspaces without auth → 401, so API is live and requires auth. Good — that confirms hosted API exists.
Let me retry the schema fetch and follow docs redirect.
Pricing JS chunk downloaded 3434 bytes but grep found nothing. It's client-side rendered; data may come from an API. Let me look at the chunk for fetch URLs, and also check the RSC payload via curl -H 'RSC: 1'.
Let me do these.
✓$ echo "=== schema retry ==="; timeout 40 curl -sS -L -o /tmp/schema.json -w "code=%{http_code} size=%{size_download} url=%{url_effective}\n" https://app.ayedo.cloud/api/v1/schema/ ; ls -la /tmp/schema.…✓└{"content":[{"text":"=== schema retry ===\nTraceback (most recent call last):\n File \"<string>\", line 3, in <module>\nFileNotFoundError: [Errno 2] No such fi…ctrl+o
echo "=== schema retry ==="; timeout 40 curl -sS -L -o /tmp/schema.json -w "code=%{http_code} size=%{size_download} url=%{url_effective}\n" https://app.ayedo.cloud/api/v1/schema/ ; ls -la /tmp/schema.json 2>/dev/null; python3 -c "
import json
d=json.load(open('/tmp/schema.json'))
print('openapi',d.get('openapi'),'title',d.get('info',{}).get('title'),'version',d.get('info',{}).get('version'))
print('paths',len(d.get('paths',{})))
print('servers',d.get('servers'))
" 2>&1 | head -20
{"content":[{"text":"=== schema retry ===\nTraceback (most recent call last):\n File \"<string>\", line 3, in <module>\nFileNotFoundError: [Errno 2] No such file or directory: '/tmp/schema.json'\n","type":"text"}]}✓$ echo "=== docs follow ==="; timeout 25 curl -sSL -o /dev/null -w "code=%{http_code} url=%{url_effective}\n" https://docs.ayedo.de/polycrate/; echo "=== pricing RSC ==="; timeout 25 curl -sS -H 'RSC: 1…✓└{"content":[{"text":"=== docs follow ===\ncode=200 url=https://ayedo.de/docs/polycrate/\n=== pricing RSC ===\n200 3391\n1:\"$Sreact.fragment\"\n2:I[7989,[],\"Cl…ctrl+o
echo "=== docs follow ==="; timeout 25 curl -sSL -o /dev/null -w "code=%{http_code} url=%{url_effective}\n" https://docs.ayedo.de/polycrate/; echo "=== pricing RSC ==="; timeout 25 curl -sS -H 'RSC: 1' https://pricing.ayedo.de -o /tmp/pricing.rsc -w "%{http_code} %{size_download}\n"; head -c 800 /tmp/pricing.rsc
{"content":[{"text":"=== docs follow ===\ncode=200 url=https://ayedo.de/docs/polycrate/\n=== pricing RSC ===\n200 3391\n1:\"$Sreact.fragment\"\n2:I[7989,[],\"ClientSegmentRoot\"]\n3:I[7696,[\"7177\",\"static/chunks/app/layout-b6ef7c74d6ef7940.js\"],\"default\"]\n4:I[9766,[],\"\"]\n5:I[8924,[],\"\"]\n7:I[7387,[\"8974\",\"static/chunks/app/page-c6c5656c4af3f070.js\"],\"default\"]\n8:I[4431,[],\"OutletBoundary\"]\na:I[5278,[],\"AsyncMetadataOutlet\"]\nc:I[4431,[],\"ViewportBoundary\"]\ne:I[4431,[],\"MetadataBoundary\"]\nf:\"$Sreact.suspense\"\n11:I[7150,[],\"\"]\n:HL[\"/_next/static/media/22a5144ee8d83bca-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n:HL[\"/_next/static/media/7d4881bb7e1bf84d-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n:HL[\"/_next/static/css/0a528038dd230085.css\",\"style\"]\n0:{\"P\":null,\"b\":\"8Pq65Y3_4KPXjFoHddv02\",\"p\":\"\",\"c\":[\"\",\"\"],\"i\":false,\"f\":[[[\"\",{\"children\":[\"__PAGE__\",{}]},\"$undefined\",\"$undefined\",true],[\"\"","type":"text"}]}›Thinking…
The schema fetch keeps failing silently — curl exits without writing -w output? Odd. Maybe the request is very slow / connection reset. Let me test with explicit timing and error output.
Meanwhile, docs at https://ayedo.de/docs/polycrate/ works (200). Let's read that — it should have quickstart and pricing info.
Pricing page is fully client-rendered with data likely fetched from an API endpoint. Let me look at the page chunk for API URLs, and also check ayedo.de/docs for pricing pages.
Let me run: schema curl with -v style error capture; fetch docs.ayedo.de polycrate content.
×$ timeout 45 curl -sS --max-time 40 -D /tmp/h.txt -o /tmp/schema.json "https://app.ayedo.cloud/api/v1/schema/"; echo "exit=$?"; head -12 /tmp/h.txt 2>/dev/null; wc -c /tmp/schema.json 2>/dev/null✓└{"content":[{"text":"curl: (28) Operation timed out after 40001 milliseconds with 0 bytes received\nexit=28\n\n\nCommand exited with code 1","type":"text"}],"de…ctrl+o
{"content":[{"text":"curl: (28) Operation timed out after 40001 milliseconds with 0 bytes received\nexit=28\n\n\nCommand exited with code 1","type":"text"}],"details":{}}✓$ timeout 25 curl -sSL https://ayedo.de/docs/polycrate/ -o /tmp/polycrate_docs.html -w "code=%{http_code} size=%{size_download}\n"; sed -e 's/<[^>]*>/ /g' /tmp/polycrate_docs.html | tr -s ' \n' ' \n' | …✓└{"content":[{"text":"code=200 size=51912\n \n \n \n \n \n \n \n \n \n Polycrate | ayedo Docs \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \…ctrl+o
timeout 25 curl -sSL https://ayedo.de/docs/polycrate/ -o /tmp/polycrate_docs.html -w "code=%{http_code} size=%{size_download}\n"; sed -e 's/<[^>]*>/ /g' /tmp/polycrate_docs.html | tr -s ' \n' ' \n' | head -c 4000
{"content":[{"text":"code=200 size=51912\n \n \n \n \n \n \n \n \n \n Polycrate | ayedo Docs \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n [{\"@context\": \"https://schema.org\", \"@type\": \"TechArticle\", \"url\": \"https://ayedo.de/docs/polycrate/\", \"name\": \"Polycrate\", \"headline\": \"Polycrate\", \"description\": \"Sovereign Cloud, Kubernetes und Managed Services aus Deutschland.\", \"keywords\": [\"Kubernetes\", \"Cloud Computing\", \"DevOps\", \"Container\", \"Managed Services\", \"App Hosting\", \"Cloud Native\"], \"inLanguage\": \"de-DE\", \"publisher\": {\"@type\": \"Organization\", \"name\": \"Ayedo Cloud Solutions GmbH\", \"logo\": {\"@type\": \"ImageObject\", \"url\": \"https://ayedo.de/ayedo-logo-color.png\"}}, \"image\": {\"@type\": \"ImageObject\", \"url\": \"https://ayedo.de/media/b900cf58-1811-49b9-8553-50da675b1025/ayedo-logo-color.png\", \"width\": 1200, \"height\": 630}, \"datePublished\": \"2026-09-09T10:07:36.544564+00:00\", \"dateModified\": \"2026-09-17T12:45:29.217511+00:00\", \"author\": {\"@type\": \"Person\", \"name\": \"Ayedo Cloud Solutions GmbH\"}}, {\"@context\": \"https://schema.org\", \"@type\": \"BreadcrumbList\", \"itemListElement\": [{\"@type\": \"ListItem\", \"position\": 1, \"name\": \"Home\", \"item\": \"https://ayedo.de/\"}, {\"@type\": \"ListItem\", \"position\": 2, \"name\": \"Docs\", \"item\": \"https://ayedo.de/docs/\"}, {\"@type\": \"ListItem\", \"position\": 3, \"name\": \"Polycrate\", \"item\": \"https://ayedo.de/docs/polycrate/\"}]}] \n \n \n \n \n \n \n \n \n \n Zum Inhalt \n \n \n \n \n \n \n \n \n \n ayedo Cloud \n \n Übersicht \n ayedo Compute Cloud \n ayedo Edge Cloud \n Markterkundung Compute Cloud \n \n \n \n Hosting \n \n Kubernetes \n SaaS Hosting \n App-Katalog \n 15 Factor App \n \n Observability \n Endpoint Monitoring \n \n Anycast DNS \n Anycast Loadbalancing \n \n S3 Storage \n Identity \n Container Registry \n Secrets Management \n Code Repository \n Delivery \n Backups \n \n Loopback \n FAQ \n Preiskalkulator \n \n \n \n Polycrate \n \n Übersicht \n Plattform \n Ansible \n \n \n \n Services \n \n Support \n Consulting \n Workshops \n \n \n \n Compliance \n \n Übersicht \n Compliance Framework \n \n GDPR / DSGVO \n NIS-2 \n DORA \n Cyber Resilience Act \n Data Act \n Cloud Sovereignty Framework \n Compliance Compass \n \n \n Use Cases \n Docs \n \n Updates \n \n Blog \n Backlog \n Newsletter \n Pressemitteilungen \n News \n RSS & JSON Feeds \n \n \n \n Unternehmen \n \n Über uns \n Public Relations \n FAQ \n \n \n \n \n \n Suche \n \n \n \n \n \n \n Menü öffnen \n \n \n \n \n \n \n Suche \n \n \n \n \n \n \n ayedo Cloud \n \n Übersicht \n ayedo Compute Cloud \n ayedo Edge Cloud \n Markterkundung Compute Cloud \n \n \n \n Hosting \n \n Kubernetes \n SaaS Hosting \n App-Katalog \n 15 Factor App \n Observability \n Endpoint Monitoring \n Anycast DNS \n Anycast Loadbalancing \n S3 Storage \n Identity \n Container Registry \n Secrets Management \n Code Repository \n Delivery \n Backups \n Loopback \n FAQ \n Preiskalkulator \n \n \n \n Polycrate \n \n Übersicht \n Plattform \n Ansible \n \n \n \n Services \n \n Support \n Consulting \n Workshops \n \n \n \n Compliance \n \n Übersicht \n Compliance Framework \n GDPR / DSGVO \n NIS-2 \n DORA \n Cyber Resilience Act \n Data Act \n Cloud Sovereignty Framework \n Compliance Compass \n \n \n Use Cases \n Docs \n \n Updates \n \n Blog \n Backlog \n Newsletter \n Pressemitteilungen \n News \n RSS & JSON Feeds \n \n \n \n Unternehmen \n \n Über uns \n Public Relations \n FAQ \n \n \n \n \n \n \n \n Dokumentation \n \n \n Startseite \n \n \n \n Polycrate \n \n \n \n Übersicht \n \n \n \n Warum Polycrate? \n \n \n \n Installation & Updates \n \n \n \n Installation \n \n \n \n Updates \n \n \n \n Erste Schritte \n \n \n \n Konzepte \n \n \n \n Der Polycrate Container \n \n \n \n Workspaces \n \n \n \n Blöcke \n \n \n \n Actions \n \n \n \n Dependencies \n \n \n \n Artefakte \n \n \n \n Vererbung \n \n \n \n Workflows \n \n \n \n Snapshot \n \n \n \n Konfiguration \n \n \n \n Namenskonventionen \n \n \n \n Workspace-Verschlüsselung \n \n \n \n Spec-Driven Development \n \n \n \n Integrationen \n \n \n \n Übersicht \n \n \n \n Observability (Logs & Metriken) \n \n \n \n Ansible \n \n \n \n Kubernetes \n \n \n \n SSH \n \n \n \n Git \n \n \n \n MCP Server \n \n \n \n Operator \n \n \n \n PolyHub \n \n \n \n Registry \n \n \n \n Day 2 Operations \n \n \n \n Recipes \n \n \n \n Production-Beispiel \n \n \n \n Cloud Migration \n \n \n \n","type":"text"}]}›Thinking…
The schema endpoint on app.ayedo.cloud times out (40s, 0 bytes) — so the live instance may block or be slow from this sandbox. But /api/v1/workspaces/ returned 401 quickly earlier. Interesting: maybe /api/v1/schema/ is large or blocked. Let me retry schema with a longer timeout in background? Instructions: don't wait for long-running commands. One retry with 20s is fine.
Anyway, the key blocker: no credentials/API token in the environment. The workflow requires an API key created in the Polycrate UI (a human step, showing secret once). So I cannot actually run authenticated examples. But I CAN build a runnable example that works once a token is provided — and I can verify the SDK installs from GitHub and the code is syntactically valid / imports work. That's a "developer workflow I can act on": pip install from git, write example script, run it → expect 401 without token, which proves the wiring works.
Wait — but is app.ayedo.cloud even an available instance for us? We got 401 on workspaces, meaning it responds. Good.
Also there's "Preiskalkulator" (price calculator) page on ayedo.de — let me get pricing info from ayedo.de docs / price calculator. Pricing is important for the answer. Let me check https://ayedo.de/en/... price calculator link. Search the nav for "Preiskalkulator" href.
Let's grep the docs html for href of Preiskalkulator and pricing.ayedo.de mentions.
Also check docs for the Polycrate docs English version for quickstart & pricing mentions.
Plan next calls:
- grep links in /tmp/polycrate_docs.html for 'preis' / 'pricing'.
- Check SDK install feasibility: pip install polycrate from git (needs network to github; may take time but it's not a service stack). Actually
pip install "polycrate @ git+https://github.com/ayedode/polycrate-sdk.git@v0.33.0#subdirectory=python/polycrate"— need to know the latest tag. Let's list tags via GitHub API.
Let me do both in parallel.
✓$ grep -oE 'href="[^"]*"' /tmp/polycrate_docs.html | sort -u | grep -iE 'preis|pricing|price|kalkul' ; echo "--- all docs links ---"; grep -oE 'href="/docs/[^"]*"' /tmp/polycrate_docs.html | sort -u | h…✓└{"content":[{"text":"href=\"/docs/polycrate/polycrate-api/pricing-business-layer/\"\nhref=\"https://pricing.ayedo.de\"\n--- all docs links ---\nhref=\"/docs/\"\…ctrl+o
grep -oE 'href="[^"]*"' /tmp/polycrate_docs.html | sort -u | grep -iE 'preis|pricing|price|kalkul' ; echo "--- all docs links ---"; grep -oE 'href="/docs/[^"]*"' /tmp/polycrate_docs.html | sort -u | head -60
{"content":[{"text":"href=\"/docs/polycrate/polycrate-api/pricing-business-layer/\"\nhref=\"https://pricing.ayedo.de\"\n--- all docs links ---\nhref=\"/docs/\"\nhref=\"/docs/delivery/\"\nhref=\"/docs/delivery/air-gap/\"\nhref=\"/docs/delivery/application-deployment/\"\nhref=\"/docs/delivery/backup-restore/\"\nhref=\"/docs/delivery/guardrails/\"\nhref=\"/docs/delivery/provided-cluster/\"\nhref=\"/docs/erste-schritte/\"\nhref=\"/docs/feed.json\"\nhref=\"/docs/index.xml\"\nhref=\"/docs/kubernetes/\"\nhref=\"/docs/kubernetes/features/\"\nhref=\"/docs/kubernetes/k3s-server/\"\nhref=\"/docs/kubernetes/k8s-1.27/\"\nhref=\"/docs/ohmyhelm/\"\nhref=\"/docs/ohmyhelm/15-factor-apps/\"\nhref=\"/docs/ohmyhelm/byoa/\"\nhref=\"/docs/ohmyhelm/chart/README/\"\nhref=\"/docs/ohmyhelm/chart/app_label/\"\nhref=\"/docs/ohmyhelm/chart/environments/\"\nhref=\"/docs/ohmyhelm/chart/extra-containers/\"\nhref=\"/docs/ohmyhelm/chart/helpers/\"\nhref=\"/docs/ohmyhelm/chart/initcontainer-and-jobs/\"\nhref=\"/docs/ohmyhelm/chart/probes/\"\nhref=\"/docs/ohmyhelm/chart/rbac/\"\nhref=\"/docs/ohmyhelm/chart/sidecar/\"\nhref=\"/docs/ohmyhelm/chart/statefulset_deployment/\"\nhref=\"/docs/ohmyhelm/examples/\"\nhref=\"/docs/ohmyhelm/getting-started/\"\nhref=\"/docs/ohmyhelm/helpers/README/\"\nhref=\"/docs/ohmyhelm/helpers/configMaps/\"\nhref=\"/docs/ohmyhelm/helpers/imageCredentials/\"\nhref=\"/docs/ohmyhelm/helpers/ingress/\"\nhref=\"/docs/ohmyhelm/helpers/namespace/\"\nhref=\"/docs/ohmyhelm/helpers/plain-manifest/\"\nhref=\"/docs/ohmyhelm/helpers/secrets/\"\nhref=\"/docs/ohmyhelm/helpers/serviceMonitor/\"\nhref=\"/docs/ohmyhelm/helpers/tls-secrets/\"\nhref=\"/docs/ohmyhelm/usage/\"\nhref=\"/docs/ohmyhelm/use-cases/\"\nhref=\"/docs/platform/\"\nhref=\"/docs/platform/best-practices/\"\nhref=\"/docs/platform/compliance/\"\nhref=\"/docs/platform/editionen/\"\nhref=\"/docs/platform/features/\"\nhref=\"/docs/platform/operations/\"\nhref=\"/docs/platform/operations/access-control/\"\nhref=\"/docs/platform/operations/access-observability/\"\nhref=\"/docs/platform/operations/capacity-management/\"\nhref=\"/docs/platform/operations/disaster-recovery/\"\nhref=\"/docs/platform/operations/ingress-edge/\"\nhref=\"/docs/platform/operations/logs-ingest/\"\nhref=\"/docs/platform/operations/maintenance/\"\nhref=\"/docs/platform/operations/managed-mongodb/\"\nhref=\"/docs/platform/operations/network-access/\"\nhref=\"/docs/platform/operations/policy-as-code/\"\nhref=\"/docs/platform/operations/private-object-storage/\"\nhref=\"/docs/platform/operations/runbooks/\"\nhref=\"/docs/platform/operations/storage/\"\nhref=\"/docs/platform/operations/troubleshooting/\"\n","type":"text"}]}✓$ timeout 25 curl -sS "https://api.github.com/repos/ayedode/polycrate-sdk/tags?per_page=20" | python3 -c "import json,sys;d=json.load(sys.stdin);print([t['name'] for t in d] if isinstance(d,list) else d…✓└{"content":[{"text":"[]\nPython 3.12.3\npip 26.2.1 from /opt/freestyle/python/lib/python3.12/site-packages/pip (python 3.12)\n","type":"text"}]}ctrl+o
timeout 25 curl -sS "https://api.github.com/repos/ayedode/polycrate-sdk/tags?per_page=20" | python3 -c "import json,sys;d=json.load(sys.stdin);print([t['name'] for t in d] if isinstance(d,list) else d)"; python3 -V; pip --version 2>&1 | head -1
›Thinking…
Important: The repo has no tags (empty tag list). The README says install pinned to a git tag v<api-version>, but no tags exist. Let me verify via git ls-remote (quick). If no tags, the documented install command fails → a real blocker for the documented quickstart. But I can install from the main branch instead: pip install "polycrate @ git+https://github.com/ayedode/polycrate-sdk.git#subdirectory=python/polycrate" or clone and pip install ./python/polycrate.
Note python 3.12 available (README requires 3.11+ ✓).
Let me:
- git ls-remote --tags (quick).
- Fetch pricing docs page: /docs/polycrate/polycrate-api/pricing-business-layer/ — that's about the "pricing business layer" in the API? Interesting — maybe the API itself has pricing endpoints. Let me read that page. Also check if there's a polycrate-api docs section (quickstart for API).
Let me also list docs links containing 'polycrate-api'.
✓$ timeout 30 git ls-remote --tags https://github.com/ayedode/polycrate-sdk.git 2>&1 | head; echo "exit=$?"; echo "--- heads ---"; timeout 30 git ls-remote --heads https://github.com/ayedode/polycrate-sd…✓└{"content":[{"text":"exit=0\n--- heads ---\ncc1b8c0da1f9b28a6bbc894c317f686aedc74d5d\trefs/heads/main\n","type":"text"}]}ctrl+o
timeout 30 git ls-remote --tags https://github.com/ayedode/polycrate-sdk.git 2>&1 | head; echo "exit=$?"; echo "--- heads ---"; timeout 30 git ls-remote --heads https://github.com/ayedode/polycrate-sdk.git 2>&1 | head
✓$ grep -oE 'href="/docs/polycrate[^"]*"' /tmp/polycrate_docs.html | sort -u✓└{"content":[{"text":"href=\"/docs/polycrate/\"\nhref=\"/docs/polycrate/actions/\"\nhref=\"/docs/polycrate/ansible/\"\nhref=\"/docs/polycrate/api/\"\nhref=\"/doc…ctrl+o
{"content":[{"text":"href=\"/docs/polycrate/\"\nhref=\"/docs/polycrate/actions/\"\nhref=\"/docs/polycrate/ansible/\"\nhref=\"/docs/polycrate/api/\"\nhref=\"/docs/polycrate/artefakte/\"\nhref=\"/docs/polycrate/best-practices/\"\nhref=\"/docs/polycrate/blocke/\"\nhref=\"/docs/polycrate/cli-referenz/\"\nhref=\"/docs/polycrate/cloud-migration/\"\nhref=\"/docs/polycrate/dependencies/\"\nhref=\"/docs/polycrate/der-polycrate-container/\"\nhref=\"/docs/polycrate/dev-and-deploy/\"\nhref=\"/docs/polycrate/erste-schritte/\"\nhref=\"/docs/polycrate/git/\"\nhref=\"/docs/polycrate/installation/\"\nhref=\"/docs/polycrate/integrationen/\"\nhref=\"/docs/polycrate/konfiguration/\"\nhref=\"/docs/polycrate/kubernetes/\"\nhref=\"/docs/polycrate/mcp/\"\nhref=\"/docs/polycrate/namenskonventionen/\"\nhref=\"/docs/polycrate/observability/\"\nhref=\"/docs/polycrate/operator/\"\nhref=\"/docs/polycrate/polycrate-api/\"\nhref=\"/docs/polycrate/polycrate-api/action-runs/\"\nhref=\"/docs/polycrate/polycrate-api/activity-timeline/\"\nhref=\"/docs/polycrate/polycrate-api/apm-unified-credential/\"\nhref=\"/docs/polycrate/polycrate-api/assistant/\"\nhref=\"/docs/polycrate/polycrate-api/audit-compliance/\"\nhref=\"/docs/polycrate/polycrate-api/authentifizierung/\"\nhref=\"/docs/polycrate/polycrate-api/backup-recovery/\"\nhref=\"/docs/polycrate/polycrate-api/block-rollout/\"\nhref=\"/docs/polycrate/polycrate-api/conditions/\"\nhref=\"/docs/polycrate/polycrate-api/criticality/\"\nhref=\"/docs/polycrate/polycrate-api/datasources/\"\nhref=\"/docs/polycrate/polycrate-api/default-data-import/\"\nhref=\"/docs/polycrate/polycrate-api/domains-dns/\"\nhref=\"/docs/polycrate/polycrate-api/downtime-timeline/\"\nhref=\"/docs/polycrate/polycrate-api/endpoint-monitoring/\"\nhref=\"/docs/polycrate/polycrate-api/incidents/\"\nhref=\"/docs/polycrate/polycrate-api/integrationen/\"\nhref=\"/docs/polycrate/polycrate-api/k8s-addons/\"\nhref=\"/docs/polycrate/polycrate-api/k8s-controlplane/\"\nhref=\"/docs/polycrate/polycrate-api/k8s-volumes/\"\nhref=\"/docs/polycrate/polycrate-api/labels-conventions/\"\nhref=\"/docs/polycrate/polycrate-api/loadbalancers/\"\nhref=\"/docs/polycrate/polycrate-api/loopback-integration/\"\nhref=\"/docs/polycrate/polycrate-api/management-commands/\"\nhref=\"/docs/polycrate/polycrate-api/mcp/\"\nhref=\"/docs/polycrate/polycrate-api/notes/\"\nhref=\"/docs/polycrate/polycrate-api/operations-dashboard/\"\nhref=\"/docs/polycrate/polycrate-api/operator-rollout-dashboard/\"\nhref=\"/docs/polycrate/polycrate-api/organisationen-workspaces/\"\nhref=\"/docs/polycrate/polycrate-api/pops-providers/\"\nhref=\"/docs/polycrate/polycrate-api/pricing-business-layer/\"\nhref=\"/docs/polycrate/polycrate-api/projects/\"\nhref=\"/docs/polycrate/polycrate-api/prometheus-metrics/\"\nhref=\"/docs/polycrate/polycrate-api/provider-accounts/\"\nhref=\"/docs/polycrate/polycrate-api/regions/\"\nhref=\"/docs/polycrate/polycrate-api/s3-buckets/\"\nhref=\"/docs/polycrate/polycrate-api/sdks/\"\nhref=\"/docs/polycrate/polycrate-api/user-management/\"\nhref=\"/docs/polycrate/polycrate-api/vulnerability-management/\"\nhref=\"/docs/polycrate/polycrate-api/wartungen/\"\nhref=\"/docs/polycrate/polycrate-api/wartungsfenster/\"\nhref=\"/docs/polycrate/polycrate-api/workspace-composer/\"\nhref=\"/docs/polycrate/polyhub/\"\nhref=\"/docs/polycrate/production-beispiel/\"\nhref=\"/docs/polycrate/recipes/\"\nhref=\"/docs/polycrate/registry/\"\nhref=\"/docs/polycrate/snapshot/\"\nhref=\"/docs/polycrate/spec-driven-development/\"\nhref=\"/docs/polycrate/ssh/\"\nhref=\"/docs/polycrate/troubleshooting/\"\nhref=\"/docs/polycrate/updates/\"\nhref=\"/docs/polycrate/vererbung/\"\nhref=\"/docs/polycrate/warum-polycrate/\"\nhref=\"/docs/polycrate/workflows/\"\nhref=\"/docs/polycrate/workspace-verschluesselung/\"\nhref=\"/docs/polycrate/workspaces/\"\n","type":"text"}]}›Thinking…
Confirmed: repo has NO tags, only main. So the documented install command (@v0.33.0) cannot be used — must install from main.
Now let's read key docs:
- /docs/polycrate/polycrate-api/ (API overview — base URL, auth)
- /docs/polycrate/polycrate-api/pricing-business-layer/ (pricing!)
- /docs/polycrate/polycrate-api/sdks/ (SDK docs — may reveal canonical install)
- /docs/polycrate/polycrate-api/authentifizierung/
Let me fetch and extract text. I'll write a small helper to strip HTML.
Let me fetch pricing-business-layer and sdks first.
✓$ cd /tmp && cat > strip.py <<'EOF'✓└{"content":[{"text":"polycrate-api/pricing-business-layer 200\npolycrate-api/sdks 200\nDokumentation\n \n \n Startseite\n \n \n \n Polycrate\n\n \n \n \n Übersi…ctrl+o
cd /tmp && cat > strip.py <<'EOF'
import re,sys,html
t=open(sys.argv[1],encoding='utf-8',errors='replace').read()
t=re.sub(r'(?s)<script.*?</script>','',t)
t=re.sub(r'(?s)<style.*?</style>','',t)
t=re.sub(r'(?s)<(nav|header|footer|svg)[^>]*>.*?</\1>','',t)
m=re.search(r'(?s)<main[^>]*>(.*?)</main>',t)
if m: t=m.group(1)
t=re.sub(r'(?s)<(h[1-6])[^>]*>',r'\n\n## ',t)
t=re.sub(r'(?s)</(p|div|li|tr|h[1-6]|pre|table)>','\n',t)
t=re.sub(r'(?s)<td[^>]*>',' | ',t)
t=re.sub(r'(?s)<[^>]+>','',t)
t=html.unescape(t)
t=re.sub(r'\n{3,}','\n\n',t)
t=re.sub(r'[ \t]{2,}',' ',t)
print(t.strip())
EOF
for p in polycrate-api/pricing-business-layer polycrate-api/sdks; do timeout 25 curl -sSL "https://ayedo.de/docs/polycrate/$p/" -o "d_$(basename $p).html" -w "$p %{http_code}\n"; done; python3 strip.py d_pricing-business-layer.html | head -120
{"content":[{"text":"polycrate-api/pricing-business-layer 200\npolycrate-api/sdks 200\nDokumentation\n \n \n Startseite\n \n \n \n Polycrate\n\n \n \n \n Übersicht\n \n \n \n Warum Polycrate?\n \n \n \n Installation & Updates\n\n \n \n \n Installation\n \n \n \n Updates\n \n \n \n Erste Schritte\n \n \n \n Konzepte\n\n \n \n \n Der Polycrate Container\n \n \n \n Workspaces\n \n \n \n Blöcke\n \n \n \n Actions\n \n \n \n Dependencies\n \n \n \n Artefakte\n \n \n \n Vererbung\n \n \n \n Workflows\n \n \n \n Snapshot\n \n \n \n Konfiguration\n \n \n \n Namenskonventionen\n \n \n \n Workspace-Verschlüsselung\n \n \n \n Spec-Driven Development\n \n \n \n Integrationen\n\n \n \n \n Übersicht\n \n \n \n Observability (Logs & Metriken)\n \n \n \n Ansible\n \n \n \n Kubernetes\n \n \n \n SSH\n \n \n \n Git\n","type":"text"}]}›Thinking…
The sidebar nav dominates. Let me improve extraction: find the article content region. Let's look at the HTML structure to find content markers (e.g., <article or a specific class). Let me grep for "article" or headings in the file.
✓$ cd /tmp; grep -oE '<(article|section|div)[^>]{0,120}' d_pricing-business-layer.html | grep -iE 'content|article|prose|doc' | head -20; echo "=== headings ==="; grep -oE '<h[1-4][^>]*>[^<]{0,80}' d_pri…✓└{"content":[{"text":"<div class=\"docs\"\n<div class=\"docs-nav-section\"\n<div class=\"docs-nav-section\"\n<div class=\"docs-nav-section\"\n<div class=\"docs-n…ctrl+o
cd /tmp; grep -oE '<(article|section|div)[^>]{0,120}' d_pricing-business-layer.html | grep -iE 'content|article|prose|doc' | head -20; echo "=== headings ==="; grep -oE '<h[1-4][^>]*>[^<]{0,80}' d_pricing-business-layer.html | head -30
{"content":[{"text":"<div class=\"docs\"\n<div class=\"docs-nav-section\"\n<div class=\"docs-nav-section\"\n<div class=\"docs-nav-section\"\n<div class=\"docs-nav-section\"\n<div class=\"docs-nav-section\"\n<div class=\"docs-nav-section\"\n<div class=\"docs-nav-section\"\n<div class=\"docs-nav-section\"\n<div class=\"docs-nav-section\"\n<div class=\"docs-nav-section\"\n<div class=\"docs-nav-section\"\n<div class=\"docs-nav-section\"\n<div class=\"docs-nav-section\"\n<div class=\"docs-nav-section\"\n<article class=\"docs-article prose\"\n=== headings ===\n<h1>Pricing & Business Layer\n<h1>Pricing & Business Layer\n<h2>Übersicht\n<h2>Kernkonzepte\n<h3>Product\n<h3>OrganizationProduct\n<h3>Support-Product & Auto-Assignment\n<h2>Region Default Products\n<h3>S3-Cluster\n<h3>Loadbalancer-Instanzen\n<h3>Berechnete Kosten\n<h3>Neu in 0.15.0: Productized Models\n<h2>Wo finden Sie was?\n<h2>Nutzen für IT-Leiter\n<h3>Services\n<h3>Unternehmen\n<h3>Legal\n","type":"text"}]}›Thinking…
There's an <article class="docs-article prose">. Let me update strip.py to extract article. Then read pricing-business-layer, sdks, api overview, and authentication docs.
Note: This "Pricing & Business Layer" doc is about the API's pricing endpoints (products, costs) — relevant! The user asks "Tell me how pricing works". This could be about the product's pricing (ayedo hosting pricing) or the API's pricing model. I should cover: (a) ayedo's commercial pricing (pricing.ayedo.de calculator, plans), and (b) the API's pricing/cost endpoints if present in the SDK.
Let's extract the article text.
✓$ cd /tmp && cat > strip.py <<'EOF'✓└{"content":[{"text":"## Pricing & Business Layer\n\n \n \n\n## Pricing & Business Layer\n\n## Übersicht\n\nDie Polycrate API verwaltet Produkte, Preisregeln und…ctrl+o
cd /tmp && cat > strip.py <<'EOF'
import re,sys,html
t=open(sys.argv[1],encoding='utf-8',errors='replace').read()
t=re.sub(r'(?s)<script.*?</script>','',t)
t=re.sub(r'(?s)<style.*?</style>','',t)
m=re.search(r'(?s)<article[^>]*>(.*?)</article>',t)
if m: t=m.group(1)
t=re.sub(r'(?s)<(h[1-6])[^>]*>',r'\n\n## ',t)
t=re.sub(r'(?s)<(pre|code)[^>]*>',r'`',t)
t=re.sub(r'(?s)</(pre|code)>',r'`\n',t)
t=re.sub(r'(?s)</(p|div|li|tr|h[1-6]|pre|table)>','\n',t)
t=re.sub(r'(?s)<td[^>]*>',' | ',t)
t=re.sub(r'(?s)<[^>]+>','',t)
t=html.unescape(t)
t=re.sub(r'\n{3,}','\n\n',t)
t=re.sub(r'[ \t]{2,}',' ',t)
print(t.strip())
EOF
python3 strip.py d_pricing-business-layer.html | head -140
{"content":[{"text":"## Pricing & Business Layer\n\n \n \n\n## Pricing & Business Layer\n\n## Übersicht\n\nDie Polycrate API verwaltet Produkte, Preisregeln und Verkaufshistorie nativ – ohne externe Systeme wie Baserow. Dieser Business Layer bildet die Grundlage für Abrechnung, Support-Zuweisung und Ressourcen-Kosten.\n\n`graph TB\n Product[Product] --> OrgProduct[OrganizationProduct]\n Product --> PricingRule[PricingRule]\n OrgProduct --> CostStatement[CostStatement]\n S3Bucket[S3 Bucket] --> OrgProduct\n LBI[Loadbalancer Instance] --> OrgProduct`\n\n## Kernkonzepte\n\n## Product\n\nEin Product ist ein verkaufbares Gut mit Preis und optionalen Features:\n\nProduct-Kind\nVerwendung\n\n | support\n | Support-Pakete (Basic, Premium, Priority) – steuert z. B. RocketChat-Channel-Berechtigung\n\n | object-storage\n | S3-Bucket-Abrechnung\n\n | loadbalancer\n | Loadbalancer-Instanz-Abrechnung\n\n | host\n | Dedizierte Server / VMs\n\n | k8scluster\n | Kubernetes-Cluster\n\n | k8sapp\n | Kubernetes-Apps\n\n | block-storage\n | PersistentVolumes (seit API 0.15.0) – siehe K8s Volumes\n\n | dns-zone-internal\n | Interne DNS-Zonen (ayedo-verwaltet, seit API 0.15.0)\n\n | dns-zone-external\n | Externe DNS-Zonen (Kunden-Provider, seit API 0.15.0)\n\n## OrganizationProduct\n\nOrganizationProduct verknüpft eine Organisation mit einem Product – inkl. Zeitraum und vereinbartem Preis:\n\nWas: Welches Produkt\n\nAn wen: Welche Organisation\n\nZu welchem Preis: `agreed_price`\n\nAb wann / bis wann: `active_from`\n, `active_until`\n\n## Support-Product & Auto-Assignment\n\nOrganisationen können ein Support-Product zugewiesen bekommen (z. B. Premium Support). Ist dies konfiguriert:\n\nAuto-Assignment: Über `SystemConfig.default_support_product`\n kann eine Organisation automatisch ein Support-Produkt erhalten, falls keins zugewiesen ist\n\nFeature-Flags: Products tragen `config.features`\n – z. B. `rocketchat_channel: true`\n für RocketChat-Integration\n\nCondition: Fehlt ein Support-Product, wo es erwartet wird, kann `ORGANIZATION_SUPPORT_PRODUCT_MISSING`\n gesetzt werden\n\n## Region Default Products\n\nS3-Buckets und Loadbalancer-Instanzen können automatisch einem Product zugeordnet werden:\n\n## S3-Cluster\n\nDefault Product: Ein S3-Cluster kann ein `default_product`\n (kind=object-storage) haben\n\nNeue Buckets: Erhalten beim Anlegen automatisch dieses Product\n\nBackfill: Bestehende Buckets ohne Product erhalten bei Reconciliation das Cluster-Default\n\nSystemConfig: `default_s3_product`\n als Vorschlag für neue Cluster\n\n## Loadbalancer-Instanzen\n\nSystemConfig: `default_loadbalancer_product`\n – wird automatisch allen Loadbalancer-Instanzen zugewiesen\n\nBerechnung: Bei Reconciliation und bei manueller Zuweisung\n\n## Berechnete Kosten\n\nS3 Bucket: `computed_cost`\n – nutzungsbasiert (KB → GB × Preis/GB)\n\nLoadbalancer Instance: `computed_cost`\n – Flat-Rate (1 Instanz × Monatspreis)\n\nHost: Über `match_host_products`\n-Command zuweisbar\n\nK8sVolume (seit 0.15.0): `computed_cost`\n – nutzungsbasiert (`capacity_bytes`\n → GB × Preis/GB). Default-Zuweisung über `SystemConfig.default_k8svolume_product`\n\nDNSZone (seit 0.15.0): Flat-Rate je Zone und Monat. Preis unterscheidet sich nach Zone-Scope:\n`scope=internal`\n → Default-Product aus `SystemConfig.default_dns_zone_internal_product`\n\n`scope=external`\n → Default-Product aus `SystemConfig.default_dns_zone_external_product`\n\nExterne Zonen sind i. d. R. teurer (Provider-Integration, höherer Pflegeaufwand)\n\n## Neu in 0.15.0: Productized Models\n\nMehrere neue Objekt-Typen wurden \"productized\": sie erben `ProductizedModelMixin`\n und können Kosten berechnen:\n\nModell\nProduct-Kind(s)\nDefault über\n\n | K8sVolume\n | `block-storage`\n\n","type":"text"}]}✓$ cd /tmp; python3 strip.py d_sdks.html | head -160✓└{"content":[{"text":"## HTTP-SDKs\n\n \n \n\n## HTTP-SDKs\n\nTypisierte HTTP-Clients für die Polycrate API in Go, Python und JavaScript/TypeScript. Ein öffentli…ctrl+o
{"content":[{"text":"## HTTP-SDKs\n\n \n \n\n## HTTP-SDKs\n\nTypisierte HTTP-Clients für die Polycrate API in Go, Python und JavaScript/TypeScript. Ein öffentliches GitHub-Repo, drei Sprachen, dieselbe OpenAPI-Beschreibung.\n\nDie Clients wrappen HTTP. Sie verwalten weder Ressourcen-Lifecycle noch Idempotenz. Die SDK-Version entspricht der API-Version (API `0.33.0`\n → Tag `v0.33.0`\n). Die Packages liegen nicht auf PyPI oder npm — Installation nur von GitHub, Tag auf die API pinnen, mit der Sie sprechen.\n\nRepo: github.com/ayedode/polycrate-sdk\n\nSprache\nPaket\nInstallation\n\n | Go\n | `github.com/ayedode/polycrate-sdk/go`\n\n | `go get github.com/ayedode/polycrate-sdk/go@v<api-version>`\n\n | Python\n | `polycrate`\n\n | `pip install \"polycrate @ git+https://github.com/ayedode/polycrate-sdk.git@v<api-version>#subdirectory=python/polycrate\"`\n\n | JavaScript / TypeScript\n | `@ayedo/polycrate-sdk`\n\n | `npm i github:ayedode/polycrate-sdk#v<api-version>:js`\n\nLive-OpenAPI: `GET /api/v1/schema/`\n. Interaktive Docs: `/api/docs/`\n.\n\n## Authentifizierung\n\nGeschützte Endpoints erwarten:\n\n``Authorization: Bearer <token>\n`\n`\n\nHTTP Basic auf `/api/v1/…`\n reicht nicht. Langlebige Keys legt die Web-UI an (Secret wird einmal angezeigt):\n\nKey\nWo in der UI\n\n | User\n | Account-Menü → API Keys (`/ui/accounts/api-keys/`\n)\n\n | Organization\n | Organisation → Tab API Keys\n\n | System\n | Administration → System API Keys (`/ui/administration/system-api-keys/`\n, nur Staff)\n\nAnlegen, Rechte und Widerruf: API-Keys & Authentifizierung.\n\nOptional ein kurzlebiges User-Token:\n\n``curl -X POST https://api.acme.corp/api/login/ \\\n -H \"Content-Type: application/json\" \\\n -d '{\"username\":\"ops@acme.corp\",\"password\":\"<password>\"}'\n`\n`\n\nDie Response enthält `token`\n. Dieselben Keys gelten für den API-MCP.\n\nBasis-URL der Instanz ohne Pfad-Suffix, z. B. `https://api.acme.corp`\n.\n\n## Go\n\nGo 1.23+.\n\n``go get github.com/ayedode/polycrate-sdk/go@v0.33.0\n`\n`\n\n``package main\n\nimport (\n \"context\"\n \"fmt\"\n \"net/http\"\n \"os\"\n\n polycrate \"github.com/ayedode/polycrate-sdk/go\"\n)\n\nfunc main() {\n token := os.Getenv(\"POLYCRATE_TOKEN\")\n client, err := polycrate.NewClientWithResponses(\"https://api.acme.corp\",\n polycrate.WithRequestEditorFn(func(_ context.Context, req *http.Request) error {\n req.Header.Set(\"Authorization\", \"Bearer \"+token)\n return nil\n }),\n )\n if err != nil {\n panic(err)\n }\n\n pageSize := 3\n resp, err := client.ApiV1WorkspacesListWithResponse(context.Background(), &polycrate.ApiV1WorkspacesListParams{\n PageSize: &pageSize,\n })\n if err != nil {\n panic(err)\n }\n if resp.JSON200 == nil {\n panic(fmt.Sprintf(\"HTTP %d\", resp.StatusCode()))\n }\n fmt.Println(resp.JSON200.Count)\n}\n`\n`\n\nJede OpenAPI-Operation wird eine Methode auf `Client`\n / `ClientWithResponses`\n (z. B. `ApiV1WorkspacesListWithResponse`\n). Erfolgreiches JSON liegt auf `JSON200`\n (oder dem passenden Statusfeld). Listen-Namen wie `WorkspaceList.Name`\n sind Pointer.\n\n## Python\n\nPython 3.11+, Abhängigkeiten `httpx`\n und `attrs`\n.\n\n``pip install \"polycrate @ git+https://github.com/ayedode/polycrate-sdk.git@v0.33.0#subdirectory=python/polycrate\"\n`\n`\n\n``from polycrate import AuthenticatedClient\nfrom polycrate.api.api.api_v1_workspaces_list import sync_detailed\n\nclient = AuthenticatedClient(\n base_url=\"https://api.acme.corp\",\n token=\"...\",\n prefix=\"Bearer\",\n)\n\nresp = sync_detailed(client=client, page_size=3)\nif resp.status_code != 200 or resp.parsed is None:\n raise SystemExit(f\"HTTP {resp.status_code}\")\nprint(resp.parsed.count, [ws.name for ws in resp.parsed.results])\n`\n`\n\nJeder Pfad wird ein Modul unter `polycrate.api.api`\n mit vier Aufrufen:\n","type":"text"}]}›Thinking…
Good. Now: pricing docs page is about the API's business/pricing layer. But the user asks "how pricing works" for the product. I should also cover commercial pricing: pricing.ayedo.de (client-rendered calculator) and maybe ayedo.de/docs/platform/editionen/ (editions) and the "Preiskalkulator" page. Let me look for pricing pages:
- https://ayedo.de/... "Preiskalkulator" href — I didn't find it in the grep of the polycrate docs page (grep for preis found only pricing-business-layer and pricing.ayedo.de). So the Preiskalkulator nav item links to https://pricing.ayedo.de.
Let me try to extract pricing data from pricing.ayedo.de. The Next.js app fetches data client-side. Look at the page chunk JS for API base URLs. Let me grep for "http" / "api" in /tmp/pricing_page.js and layout chunk.
Also try /docs/platform/editionen/ for edition pricing.
Then: check whether app.ayedo.cloud is a real usable instance. /api/v1/workspaces/ → 401 (responds fast). Good enough to demonstrate 401 without token.
Next steps:
- grep pricing JS for endpoints.
- fetch editionen doc.
Then build the example:
- Install Python SDK from GitHub main branch into a venv (pip install may take ~20-60s; acceptable, not a long-running service).
- Write example script
examples/list_workspaces.pythat reads POLYCRATE_BASE_URL / POLYCRATE_TOKEN, uses the SDK, handles 401 gracefully. - Run it without token to show it wires up correctly (expect 401 → clean error message). That's a real, verifiable run against the hosted API, no local stack.
- Write README with quickstart, pricing notes, and the blockers (no tags published, no credentials).
Let me check what the SDK's python package looks like — dependencies httpx, attrs. Install from git main.
First, gather pricing info in parallel with starting the SDK install? Install depends on nothing, so parallel is fine. Let's do: (a) pricing JS grep + editionen doc, (b) create venv and pip install from git main.
Actually the venv install: python3 -m venv .venv then pip install git+https...#subdirectory=python/polycrate. Let's do it with a timeout of ~180s. That's a build/install, not a service stack. Fine.
Let me kick both.
✓$ cd /tmp; grep -oE 'https?://[a-zA-Z0-9./_-]+' pricing_page.js | sort -u | head -20; echo "--- fetch/api strings ---"; grep -oE '"/[a-zA-Z0-9./_-]{2,40}"' pricing_page.js | sort -u | head -30; echo "--…✓└{"content":[{"text":"--- fetch/api strings ---\n--- keys ---\n","type":"text"}]}ctrl+o
cd /tmp; grep -oE 'https?://[a-zA-Z0-9./_-]+' pricing_page.js | sort -u | head -20; echo "--- fetch/api strings ---"; grep -oE '"/[a-zA-Z0-9./_-]{2,40}"' pricing_page.js | sort -u | head -30; echo "--- keys ---"; grep -oE '(apiUrl|baseUrl|API_URL|NEXT_PUBLIC[A-Z_]*)' pricing_page.js | sort -u | head
✓$ cd /sandbox/repo && python3 -m venv .venv >/dev/null 2>&1 && timeout 240 .venv/bin/pip install -q "polycrate @ git+https://github.com/ayedode/polycrate-sdk.git@main#subdirectory=python/polycrate" 2>&1…✓└{"content":[{"text":"install_exit=0\npolycrate OK /sandbox/repo/.venv/lib/python3.12/site-packages/polycrate/__init__.py\n","type":"text"}]}ctrl+o
cd /sandbox/repo && python3 -m venv .venv >/dev/null 2>&1 && timeout 240 .venv/bin/pip install -q "polycrate @ git+https://github.com/ayedode/polycrate-sdk.git@main#subdirectory=python/polycrate" 2>&1 | tail -5; echo "install_exit=$?"; .venv/bin/python -c "import polycrate, httpx, attrs; print('polycrate OK', polycrate.__file__)"
›Thinking…
SDK installed from git main. Now check the package structure and the workspaces list module exists.
Pricing page JS has no URLs — maybe it's in another chunk (layout or a data chunk). Let me look for chunk list and search all chunks for "€" or price data. Alternatively, the calculator may compute from a static JSON. Let me fetch the build manifest and grep chunks for pricing keywords.
Let me do both in parallel: (a) inspect installed SDK: list api modules, check api_v1_workspaces_list, client class signature, version. (b) pricing.ayedo.de chunks scan.
✓$ cd /sandbox/repo && ls .venv/lib/python3.12/site-packages/polycrate/ && .venv/bin/pip show polycrate | head -6 && ls .venv/lib/python3.12/site-packages/polycrate/api/api/ | wc -l && ls .venv/lib/pytho…✓└{"content":[{"text":"__init__.py\n__pycache__\napi\nclient.py\nerrors.py\nmodels\npy.typed\ntypes.py\nName: polycrate\nVersion: 0.37.0\nSummary: A client librar…ctrl+o
cd /sandbox/repo && ls .venv/lib/python3.12/site-packages/polycrate/ && .venv/bin/pip show polycrate | head -6 && ls .venv/lib/python3.12/site-packages/polycrate/api/api/ | wc -l && ls .venv/lib/python3.12/site-packages/polycrate/api/api/ | grep -iE 'workspaces_list|pricing|product' | head -20
{"content":[{"text":"__init__.py\n__pycache__\napi\nclient.py\nerrors.py\nmodels\npy.typed\ntypes.py\nName: polycrate\nVersion: 0.37.0\nSummary: A client library for accessing Polycrate API\nHome-page: \nAuthor: Ayedo Cloud Solutions GmbH\nAuthor-email: \nERROR: Pipe to stdout was broken\nException ignored in: <_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8'>\nBrokenPipeError: [Errno 32] Broken pipe\n804\napi_v1_catalogue_apps_sync_vulnerability_products_create.py\napi_v1_pricing_calculator_states_archive_create.py\napi_v1_pricing_calculator_states_create.py\napi_v1_pricing_calculator_states_destroy.py\napi_v1_pricing_calculator_states_formalize_create.py\napi_v1_pricing_calculator_states_list.py\napi_v1_pricing_calculator_states_metrics_retrieve.py\napi_v1_pricing_calculator_states_partial_update.py\napi_v1_pricing_calculator_states_retrieve.py\napi_v1_pricing_calculator_states_update.py\napi_v1_pricing_cost_statements_archive_create.py\napi_v1_pricing_cost_statements_create.py\napi_v1_pricing_cost_statements_destroy.py\napi_v1_pricing_cost_statements_focus_retrieve.py\napi_v1_pricing_cost_statements_generate_create.py\napi_v1_pricing_cost_statements_list.py\napi_v1_pricing_cost_statements_metrics_retrieve.py\napi_v1_pricing_cost_statements_partial_update.py\napi_v1_pricing_cost_statements_retrieve.py\napi_v1_pricing_cost_statements_update.py\n","type":"text"}]}✓$ cd /tmp && timeout 20 curl -sS https://pricing.ayedo.de -o pr.html; grep -oE '/_next/static/chunks/[a-zA-Z0-9./_-]+\.js' pr.html | sort -u > chunks.txt; wc -l chunks.txt; while read c; do timeout 20 c…✓└{"content":[{"text":"7 chunks.txt\nch_4bd1b696-f785427dddbba9fb.js\n---sizes---\n-rw------- 1 root root 173382 Sep 20 14:55 ch_1255-b28ea36bf0cdbd65.js\n-rw----…ctrl+o
cd /tmp && timeout 20 curl -sS https://pricing.ayedo.de -o pr.html; grep -oE '/_next/static/chunks/[a-zA-Z0-9./_-]+\.js' pr.html | sort -u > chunks.txt; wc -l chunks.txt; while read c; do timeout 20 curl -sS "https://pricing.ayedo.de$c" -o "ch_$(basename $c)"; done < chunks.txt; grep -lE '€|EUR|monat|month' ch_*.js 2>/dev/null | head; echo "---sizes---"; ls -la ch_*.js | head -20
{"content":[{"text":"7 chunks.txt\nch_4bd1b696-f785427dddbba9fb.js\n---sizes---\n-rw------- 1 root root 173382 Sep 20 14:55 ch_1255-b28ea36bf0cdbd65.js\n-rw------- 1 root root 173020 Sep 20 14:55 ch_4bd1b696-f785427dddbba9fb.js\n-rw------- 1 root root 802 Sep 20 14:55 ch_layout-b6ef7c74d6ef7940.js\n-rw------- 1 root root 560 Sep 20 14:55 ch_main-app-05a5fe279f4f6e20.js\n-rw------- 1 root root 3434 Sep 20 14:55 ch_page-c6c5656c4af3f070.js\n-rw------- 1 root root 112594 Sep 20 14:55 ch_polyfills-42372ed130431b0a.js\n-rw------- 1 root root 3652 Sep 20 14:55 ch_webpack-e0af4d03ef8c2dab.js\n","type":"text"}]}›Thinking…
SDK version 0.37.0 installed from main — so the live API version is 0.37.0, and the README's example tags (v0.33.0) don't exist. Good finding.
The pricing calculator has API endpoints in the SDK: /api/v1/pricing/calculator/states/, /api/v1/pricing/cost-statements/, products, etc. So pricing is exposed via the API. Let me check the pricing docs pages in the ayedo docs for the calculator and cost statements to describe pricing accurately.
The pricing.ayedo.de site: the page chunk 3434 bytes probably imports a lazy chunk. The React chunk contains "month" strings but that's the React library. Data likely fetched from an API at runtime (maybe app.ayedo.cloud pricing calculator endpoints, requiring auth?).
Better approach for pricing: read ayedo docs pages:
- /docs/polycrate/polycrate-api/pricing-business-layer/ (already partially read — got the rest)
- maybe there's a docs page for the price calculator: search docs index for "preis" pages.
Let me check the docs sitemap/index for pricing-related pages, plus read the rest of pricing-business-layer article ("Wo finden Sie was?" section).
Also: check the SDK models for PricingCalculatorState and Product to describe fields concretely — that's authoritative and quick (local files).
Let me run in parallel: (a) rest of pricing-business-layer text (tail). (b) grep docs index.xml for preis/pricing pages. (c) SDK: inspect models Product, PricingCalculatorState fields, and the workspaces list module signature.
✓$ cd /tmp; python3 strip.py d_pricing-business-layer.html | sed -n '140,260p'✓└{"content":[{"text":"\n | `SystemConfig.default_k8svolume_product`\n\n | DNSZone (internal)\n | `dns-zone-internal`\n\n | `SystemConfig.default_dns_zone_interna…ctrl+o
{"content":[{"text":"\n | `SystemConfig.default_k8svolume_product`\n\n | DNSZone (internal)\n | `dns-zone-internal`\n\n | `SystemConfig.default_dns_zone_internal_product`\n\n | DNSZone (external)\n | `dns-zone-external`\n\n | `SystemConfig.default_dns_zone_external_product`\n\nBillable Hours aus Timetracking\nDie Zeiterfassung auf Projekten über Notes liefert die abrechenbaren Stunden für Support/Consulting – ergänzend zu den ressourcenbasierten Produktkosten oben.\n\n## Wo finden Sie was?\n\nAufgabe\nOrt in der Web-UI\n\n | Produkte verwalten\n | Administration → Products\n\n | Organisation-Produkte zuweisen\n | Organisation öffnen → Products-Sektion\n\n | System-Defaults setzen\n | System Config → Default Products\n\n | S3-Cluster Default setzen\n | S3-Cluster bearbeiten → Default Product\n\n | Kosten einsehen\n | In den Detail-Ansichten (Bucket, Loadbalancer Instance)\n\n## Nutzen für IT-Leiter\n\nKosten-Übersicht: Welche Ressourcen verursachen welche Kosten?\n\nSupport-Staffelung: Welche Organisationen haben welches Support-Level?\n\nAbrechnungsgrundlage: Verkaufshistorie und Cost-Statements für Abrechnungsprozesse\n","type":"text"}]}✓$ cd /sandbox/repo; ls .venv/lib/python3.12/site-packages/polycrate/models/ | grep -iE '^(product|pricing)' | head -30; echo "=== Product fields ==="; grep -E "^ [a-z_]+:" .venv/lib/python3.12/site-p…✓└{"content":[{"text":"pricing_calculator_state_detail.py\npricing_calculator_state_detail_created.py\npricing_calculator_state_detail_created_created_by_type_0.p…ctrl+o
cd /sandbox/repo; ls .venv/lib/python3.12/site-packages/polycrate/models/ | grep -iE '^(product|pricing)' | head -30; echo "=== Product fields ==="; grep -E "^ [a-z_]+:" .venv/lib/python3.12/site-packages/polycrate/models/product.py 2>/dev/null | head -25
{"content":[{"text":"pricing_calculator_state_detail.py\npricing_calculator_state_detail_created.py\npricing_calculator_state_detail_created_created_by_type_0.py\npricing_calculator_state_detail_deleted_by_user_type_0.py\npricing_calculator_state_detail_last_action_run_type_0.py\npricing_calculator_state_detail_organization_type_0.py\npricing_calculator_state_detail_organization_type_0_created_by_type_0.py\npricing_calculator_state_detail_request.py\npricing_calculator_state_detail_workspace_type_0.py\npricing_calculator_state_detail_workspace_type_0_created_by_type_0.py\npricing_calculator_state_list.py\npricing_calculator_state_list_active_condition_instances_item.py\npricing_calculator_state_list_created.py\npricing_calculator_state_list_created_created_by_type_0.py\npricing_calculator_state_list_organization_type_0.py\npricing_calculator_state_list_organization_type_0_created_by_type_0.py\npricing_calculator_state_list_workspace_type_0.py\npricing_calculator_state_list_workspace_type_0_created_by_type_0.py\npricing_quote_app_detail.py\npricing_quote_app_detail_created.py\npricing_quote_app_detail_created_created_by_type_0.py\npricing_quote_app_detail_deleted_by_user_type_0.py\npricing_quote_app_detail_last_action_run_type_0.py\npricing_quote_app_detail_organization_type_0.py\npricing_quote_app_detail_organization_type_0_created_by_type_0.py\npricing_quote_app_detail_request.py\npricing_quote_app_detail_workspace_type_0.py\npricing_quote_app_detail_workspace_type_0_created_by_type_0.py\npricing_quote_app_list.py\npricing_quote_app_list_active_condition_instances_item.py\n=== Product fields ===\n","type":"text"}]}›Thinking…
There are pricing calculator + quote models in the SDK. Let me look at product.py model path and pricing calculator state request fields, plus cost statement. And find the docs page for the pricing calculator (maybe /docs/polycrate/polycrate-api/... or a page about "Preiskalkulator" under /docs/platform/?). Let me check the docs index for pages mentioning "Kalkulator".
Also, the pricing.ayedo.de calculator is presumably backed by these API endpoints (/api/v1/pricing/calculator/states/, formalize, quotes). Nice: I could build the example around pricing calculator! But those endpoints likely need auth (staff?).
Let me check the models quickly: pricing_calculator_state_detail_request fields, product model file name.
Also check whether there's a public/unauthenticated endpoint. Let me check which endpoints are public: e.g., /api/login/. Probably only login is public.
Let me now: (a) inspect key model fields (product, pricing calculator state request, cost statement list). (b) grep docs for the pricing calculator doc page and commercial pricing info (editionen page which may include prices).
Then write the example.
Example design (Python, since SDK installed):
examples/ayedo_quickstart.py:- Reads POLYCRATE_BASE_URL (default https://app.ayedo.cloud) and POLYCRATE_TOKEN.
- If no token: performs an unauthenticated probe (
GET /api/v1/workspaces/) to prove connectivity and prints the 401 + where to get a key. Actually better: always attempt the authed call and handle 401/403 with a helpful message. - Lists workspaces (page_size=3), prints count and names.
- Also a second script
examples/pricing_report.pythat lists products/cost statements? That requires staff perms maybe. Keep it simple: one script with subcommands? Stay light: two small scripts, or one script with--resource.
Let me check the products list module name and cost statements list params.
Also verify with a real run: no token → 401 handled. And test the base URL: app.ayedo.cloud. Also verify against a bogus base to ensure error handling. Let me check if app.ayedo.cloud is the right hosted instance; docs mention https://api.acme.corp placeholder and README says https://app.ayedo.cloud. We got 401 from app.ayedo.cloud/api/v1/workspaces/ → live instance.
Let me also check /api/login/ responds (POST with empty body → 400?) to confirm the endpoint exists — quick, no long waits.
✓$ cd /sandbox/repo; ls .venv/lib/python3.12/site-packages/polycrate/models/ | grep -E '^product' | head; echo "=== cost statement list fields ==="; grep -E "^ [a-z_]+:" .venv/lib/python3.12/site-pack…✓└{"content":[{"text":"product_detail.py\nproduct_detail_created.py\nproduct_detail_created_created_by_type_0.py\nproduct_detail_deleted_by_user_type_0.py\nproduc…ctrl+o
cd /sandbox/repo; ls .venv/lib/python3.12/site-packages/polycrate/models/ | grep -E '^product' | head; echo "=== cost statement list fields ==="; grep -E "^ [a-z_]+:" .venv/lib/python3.12/site-packages/polycrate/models/cost_statement_list.py 2>/dev/null | head -20; echo "=== pricing calc state request ==="; grep -E "^ [a-z_]+:" .venv/lib/python3.12/site-packages/polycrate/models/pricing_calculator_state_detail_request.py | head -20
{"content":[{"text":"product_detail.py\nproduct_detail_created.py\nproduct_detail_created_created_by_type_0.py\nproduct_detail_deleted_by_user_type_0.py\nproduct_detail_last_action_run_type_0.py\nproduct_detail_organization_type_0.py\nproduct_detail_organization_type_0_created_by_type_0.py\nproduct_detail_request.py\nproduct_detail_workspace_type_0.py\nproduct_detail_workspace_type_0_created_by_type_0.py\n=== cost statement list fields ===\n id: UUID\n name: str\n state: LastStateEnum\n labels: Any\n conditions: Any\n condition_instance_count: int\n active_condition_instances: list[CostStatementListActiveConditionInstancesItem]\n organization: CostStatementListOrganizationType0 | None\n organization_priority: bool\n workspace: CostStatementListWorkspaceType0 | None\n created: CostStatementListCreated\n archived: bool\n reconciliation_running: bool\n effective_criticality: EffectiveCriticalityEnum | None\n url: str\n period_start: datetime.datetime\n period_end: datetime.datetime\n status: CostStatementStatusEnum | Unset = UNSET\n total_net: str | Unset = UNSET\n currency: str | Unset = UNSET\n=== pricing calc state request ===\n name: str | Unset = UNSET\n display_name: None | str | Unset = UNSET\n labels: Any | Unset = UNSET\n annotations: Any | Unset = UNSET\n debug_mode: bool | Unset = UNSET\n provider: ProviderEnum | Unset = UNSET\n provider_reference: None | str | Unset = UNSET\n provider_id: None | str | Unset = UNSET\n reconciliation_enabled: bool | Unset = UNSET\n platform_service: bool | Unset = UNSET\n kind: GenericObjectKindEnum | Unset = UNSET\n tolerations: Any | Unset = UNSET\n archived: bool | Unset = UNSET\n archived_at: datetime.datetime | None | Unset = UNSET\n archived_reason: None | str | Unset = UNSET\n criticality: BlankEnum | CriticalityEnum | None | Unset = UNSET\n target_availability: None | str | Unset = UNSET\n slo_target: None | str | Unset = UNSET\n slo_availability: str | Unset = UNSET\n sla_target: None | str | Unset = UNSET\n","type":"text"}]}✓$ cd /tmp; timeout 25 curl -sSL https://ayedo.de/docs/index.xml -o docsindex.xml -w "%{http_code}\n"; grep -oE '<loc>[^<]*</loc>' docsindex.xml | sed 's/<[^>]*>//g' | grep -iE 'preis|pricing|edition|pri…✓└{"content":[{"text":"200\n","type":"text"}]}ctrl+o
cd /tmp; timeout 25 curl -sSL https://ayedo.de/docs/index.xml -o docsindex.xml -w "%{http_code}\n"; grep -oE '<loc>[^<]*</loc>' docsindex.xml | sed 's/<[^>]*>//g' | grep -iE 'preis|pricing|edition|price|kalkul' | head -20
›Thinking…
Docs index has no pricing/edition pages matching (the grep returned nothing). Let me check the docs index for "platform/editionen" — I saw href="/docs/platform/editionen/" earlier on the polycrate docs page nav. That's "Editionen" (editions) which likely describes tiers/pricing of the platform. Let me fetch it and extract text (it may contain price info). Also fetch /docs/polycrate/polycrate-api/ overview for the API base/auth summary.
Also check products endpoint module names for listing products: api_v1_products_list.
Parallel: (a) fetch editionen + api overview docs, extract text. (b) check SDK modules: api_v1_products_list, api_v1_workspaces_list signature; also check if there's an unauthenticated/public endpoint e.g. api_login.
✓$ cd /tmp; for p in platform/editionen polycrate/polycrate-api; do timeout 25 curl -sSL "https://ayedo.de/docs/$p/" -o "x_$(echo $p|tr '/' '_').html" -w "$p %{http_code}\n"; done; python3 strip.py x_pla…✓└{"content":[{"text":"platform/editionen 200\npolycrate/polycrate-api 200\n## Betriebsmodelle\n\n \n \n\n## Betriebsmodelle\n\nDie ayedo Software Delivery Platfo…ctrl+o
cd /tmp; for p in platform/editionen polycrate/polycrate-api; do timeout 25 curl -sSL "https://ayedo.de/docs/$p/" -o "x_$(echo $p|tr '/' '_').html" -w "$p %{http_code}\n"; done; python3 strip.py x_platform_editionen.html | head -80
{"content":[{"text":"platform/editionen 200\npolycrate/polycrate-api 200\n## Betriebsmodelle\n\n \n \n\n## Betriebsmodelle\n\nDie ayedo Software Delivery Platform (SDP) ist das Produkt. Sie wird in drei Betriebsmodellen angeboten — dieselben SDP-Bausteine, unterschieden nach Isolation, Infrastruktur und Preisstufe.\n\nMarketing-Übersicht: ayedo.de/platform/.\n\n## Was die SDP umfasst\n\nDie SDP bündelt Managed Kubernetes, Identity, CI/CD, GitOps, Registry, Secrets und Observability auf Platform- und Workload-Clustern:\n\nEbene\nRolle\n\n | Platform Cluster\n | Zentrale Platform-Services (Identity, GitLab, Argo CD, Harbor, OpenBao, Observability) — getrennt von Fachanwendungen\n\n | Workload Cluster\n | Fachanwendungen und Managed Apps auf Managed Kubernetes; nutzen die Platform-Services\n\n | Polycrate\n | Orchestrierung von Aufbau und Day-2-Ops (CLI/API). Kunden arbeiten primär mit den SDP-Apps, nicht mit Polycrate selbst\n\nCluster-Fundament (Cilium, Kyverno, Cert-Manager, Velero, External Secrets, Ingress/Gateway): Komponenten & Features.\n\n## ayedo Cloud\n\nMulti-Tenant · Compute auf ayedo-Accounts\n\nSie nutzen zentrale Shared-Instanzen der ayedo Cloud Services (u. a. ayedo ID, GitLab, Argo CD, Harbor, OpenBao, Grafana). Tenants isolieren Ihre Umgebung. Managed Kubernetes steht in vielen Regionen bei verschiedenen Providern zur Verfügung — die Compute-Ressourcen laufen auf ayedo-Accounts.\n\nEigenschaften:\n\nSchneller Einstieg, minimaler Betriebsaufwand\n\nMandantenfähige Shared Platform-Services\n\nTenant-Isolation statt eigenem Platform Cluster\n\nPassend für Teams ohne eigene Infrastruktur-Pflicht\n\nIdeal für:\n\nSchneller Start und PoCs\n\nTeams ohne dedizierte Platform-Ops\n\nWorkloads ohne Single-Tenant-/Air-Gap-Anforderung\n\n## Dedicated\n\nSingle-Tenant · von ayedo betrieben\n\nSie erhalten ein eigenes Platform Cluster, exklusiv für Ihr Unternehmen. Alle SDP-Apps laufen als dedizierte Instanzen — höhere Isolation und individuelle SLAs.\n\nEigenschaften:\n\nEigenes Platform Cluster (keine Shared Multi-Tenant-Instanzen)\n\nDedizierte SDP-Apps (Identity, GitLab, Argo CD, Harbor, OpenBao, Observability, …)\n\nGeeignet für regulierte Umgebungen\n\nIndividuelle SLAs und Betriebsabsprachen\n\nIdeal für:\n\nRegulierte Branchen und erhöhte Isolation\n\nOrganisationen mit eigenen Compliance-/Audit-Anforderungen\n\nDedizierte Betriebsflächen ohne eigene Infrastruktur stellen zu müssen\n\n## BYOC / On-Premises\n\nIhre Infrastruktur · dieselbe Betriebsstufe\n","type":"text"}]}✓$ cd /sandbox/repo; ls .venv/lib/python3.12/site-packages/polycrate/api/api/ | grep -E '^api_v1_(products|workspaces|pricing_cost_statements)_list' ; echo "=== workspaces list signature ==="; grep -E "^…✓└{"content":[{"text":"api_v1_pricing_cost_statements_list.py\napi_v1_workspaces_list.py\n=== workspaces list signature ===\ndef _get_kwargs(\n *,\n archive…ctrl+o
cd /sandbox/repo; ls .venv/lib/python3.12/site-packages/polycrate/api/api/ | grep -E '^api_v1_(products|workspaces|pricing_cost_statements)_list' ; echo "=== workspaces list signature ==="; grep -E "^def sync_detailed|^def _get_kwargs|^\s+(page_size|search|ordering)" -A2 .venv/lib/python3.12/site-packages/polycrate/api/api/api_v1_workspaces_list.py | head -30; echo "=== login module? ==="; ls .venv/lib/python3.12/site-packages/polycrate/api/api/ | grep -i login | head
{"content":[{"text":"api_v1_pricing_cost_statements_list.py\napi_v1_workspaces_list.py\n=== workspaces list signature ===\ndef _get_kwargs(\n *,\n archived: bool | Unset = UNSET,\n--\n ordering: str | Unset = UNSET,\n organization_name: str | Unset = UNSET,\n organizations: list[list[UUID]] | Unset = UNSET,\n--\n page_size: int | Unset = UNSET,\n platform_service: bool | Unset = UNSET,\n pop: list[UUID] | Unset = UNSET,\n search: str | Unset = UNSET,\n state: ApiV1WorkspacesListState | Unset = UNSET,\n state_not: ApiV1WorkspacesListStateNot | Unset = UNSET,\n--\ndef sync_detailed(\n *,\n client: AuthenticatedClient,\n--\n ordering: str | Unset = UNSET,\n organization_name: str | Unset = UNSET,\n organizations: list[list[UUID]] | Unset = UNSET,\n--\n page_size: int | Unset = UNSET,\n platform_service: bool | Unset = UNSET,\n pop: list[UUID] | Unset = UNSET,\n search: str | Unset = UNSET,\n state: ApiV1WorkspacesListState | Unset = UNSET,\n state_not: ApiV1WorkspacesListStateNot | Unset = UNSET,\n--\n=== login module? ===\napi_login_create.py\n","type":"text"}]}›Thinking…
Good. No api_v1_products_list module? Let me check: ls | grep products. Earlier grep only matched cost_statements and workspaces for the pattern ^api_v1_(products|workspaces|pricing_cost_statements)_list — products_list didn't appear, so maybe products endpoints are named differently (e.g., api_v1_pricing_products_list). Let me check.
Also there's api_login_create — the login endpoint is in the SDK (unauthenticated client).
Now let me finalize pricing understanding:
- Commercial: three operating models (ayedo Cloud multi-tenant, Dedicated single-tenant, BYOC/on-prem) at different price tiers ("Preisstufe"); pricing calculator at pricing.ayedo.de. Let me try to get actual numbers from pricing.ayedo.de — it's client-rendered; the data may be fetched from an API. Let me check the page chunk for a dynamic import of a data chunk. Alternatively check if there's a Next.js data endpoint or the RSC payload for the route with query params. It bailed out to client-side rendering, so the numbers come from a client fetch. Let me search the big chunks (1255, 4bd1b...) for "pricing" / URLs.
Actually, the calculator likely calls the Polycrate API /api/v1/pricing/calculator/... — which needs auth. So public price numbers may not be available. I'll note that plainly: prices are computed in the calculator (login required?) — let me test if pricing.ayedo.de works without login... can't run JS. Let me check for an api endpoint by grepping chunks for 'api' hostnames.
- API-level pricing (Business Layer): Products (kinds: support, object-storage, loadbalancer, host, k8scluster, k8sapp, block-storage, dns-zone-*), OrganizationProduct with agreed_price + active_from/until, computed_cost (usage-based for S3/volumes: GB × price/GB; flat-rate monthly for LB instances and DNS zones), CostStatements (period_start/end, total_net, currency), Pricing Calculator states + quotes (pricing_quote_app), billable hours from timetracking notes.
Let me check the pricing calculator doc pages in the SDK/API docs — is there a docs page "pricing-calculator"? The list showed pricing-business-layer only. Maybe quote docs elsewhere. Let me grep docs index for 'quote'/'kalkulator'.
Then write files.
Let me do: (a) grep SDK for products endpoints & pricing endpoints list (names). (b) grep docs index for quote/calculator pages; and check pricing.ayedo.de chunks for API host.
✓$ cd /sandbox/repo; ls .venv/lib/python3.12/site-packages/polycrate/api/api/ | grep -E 'pricing' | sed 's/^api_v1_//;s/\.py$//' | awk -F'_(create|list|retrieve|update|partial_update|destroy)$' '{print $…✓└{"content":[{"text":"pricing_calculator_states\npricing_calculator_states_archive\npricing_calculator_states_formalize\npricing_calculator_states_metrics\nprici…ctrl+o
cd /sandbox/repo; ls .venv/lib/python3.12/site-packages/polycrate/api/api/ | grep -E 'pricing' | sed 's/^api_v1_//;s/\.py$//' | awk -F'_(create|list|retrieve|update|partial_update|destroy)$' '{print $1}' | sort -u; echo "=== products ==="; ls .venv/lib/python3.12/site-packages/polycrate/api/api/ | grep -E 'product' | grep -E '_list\.py$' | head
{"content":[{"text":"pricing_calculator_states\npricing_calculator_states_archive\npricing_calculator_states_formalize\npricing_calculator_states_metrics\npricing_cost_statements\npricing_cost_statements_archive\npricing_cost_statements_focus\npricing_cost_statements_generate\npricing_cost_statements_metrics\npricing_cost_statements_void\npricing_organization_products\npricing_organization_products_archive\npricing_organization_products_change_price\npricing_organization_products_metrics\npricing_products\npricing_products_archive\npricing_products_metrics\npricing_products_reconcile\npricing_quote_apps\npricing_quote_apps_archive\npricing_quote_apps_metrics\npricing_quote_workspaces\npricing_quote_workspaces_archive\npricing_quote_workspaces_metrics\npricing_quotes\npricing_quotes_archive\npricing_quotes_metrics\npricing_rules\npricing_rules_archive\npricing_rules_metrics\n=== products ===\napi_v1_pricing_organization_products_list.py\napi_v1_pricing_products_list.py\napi_v1_vulnerability_products_list.py\n","type":"text"}]}✓$ cd /tmp; grep -oE '(https?://[a-z0-9.-]+|"/api/[a-z0-9/_-]+")' ch_1255-*.js ch_4bd1b*.js ch_page-*.js 2>/dev/null | sort -u | head -20; echo "--- docs index quote/calc ---"; grep -oE '<loc>[^<]*</loc>…✓└{"content":[{"text":"ch_1255-b28ea36bf0cdbd65.js:https://nextjs.org\nch_1255-b28ea36bf0cdbd65.js:https://react.dev\nch_4bd1b696-f785427dddbba9fb.js:http://www.w…ctrl+o
cd /tmp; grep -oE '(https?://[a-z0-9.-]+|"/api/[a-z0-9/_-]+")' ch_1255-*.js ch_4bd1b*.js ch_page-*.js 2>/dev/null | sort -u | head -20; echo "--- docs index quote/calc ---"; grep -oE '<loc>[^<]*</loc>' docsindex.xml | sed 's/<[^>]*>//g' | grep -iE 'quote|calc|kalkul|cost' | head
{"content":[{"text":"ch_1255-b28ea36bf0cdbd65.js:https://nextjs.org\nch_1255-b28ea36bf0cdbd65.js:https://react.dev\nch_4bd1b696-f785427dddbba9fb.js:http://www.w3.org\nch_4bd1b696-f785427dddbba9fb.js:https://react.dev\n--- docs index quote/calc ---\n","type":"text"}]}›Thinking…
The pricing.ayedo.de chunks contain no API host — so the calculator's data must come from somewhere else (maybe a lazily-loaded chunk after interaction, or it requires login and calls app.ayedo.cloud). I can't get concrete euro figures without a browser. I'll say so plainly rather than invent numbers.
Wait — maybe the page chunk imports a lazy chunk via webpack chunk ids not listed in the HTML. The page chunk (3434 bytes) is tiny. Let me quickly check the page chunk contents for chunk ids/require.e. Actually it's fine — I have limited value there. But it'd be good to state whether prices are public. Let me try one more thing: fetch pricing.ayedo.de/en or a known Next data route. Eh. Let me check page chunk content quickly (it's small) to see what it renders — maybe it's just a redirect/iframe to the Polycrate pricing calculator UI.
Then build the example files. Let me check the page chunk.
Now, the plan for deliverables in /sandbox/repo:
README.md— quickstart: what ayedo/Polycrate is, install SDK from GitHub main (tags don't exist), env vars, run example, what pricing means, and manageability assessment + blockers.examples/ayedo_quickstart.py— workspaces list via SDK, graceful 401 handling.examples/pricing_snapshot.py— lists pricing products + cost statements (read-only) to show the pricing layer via SDK. Requires token; handles 403.requirements.txtorpyproject.toml? Keep light:requirements.txtwith the git URL pinned to main + commit hash? Better: pin to the resolved commit cc1b8c0... Actually pip supportsgit+https://...@cc1b8c0#subdirectory=.... Pinning to a commit is more reproducible thanmain. But docs say pin to API version tag; tags don't exist → pin commit. I'll use the commit SHA and note it..env.example? .gitignore doesn't ignore .env — careful not to commit secrets. I'll add.envto .gitignore.
Let me verify the scripts actually run: without a token, they should exit with a clear message. Let me also test with a fake token against app.ayedo.cloud to prove the HTTP path works (expect 401). And test connectivity failure handling with a bogus base URL.
Note the schema endpoint timed out — that's a note: GET /api/v1/schema/ from this sandbox timed out (40s), while /api/v1/workspaces/ returned 401 fast. Possibly large response or blocked. I'll mention it as a minor blocker for schema-driven introspection.
Let's check page chunk, then write files.
✓$ cd /tmp; head -c 3000 ch_page-c6c5656c4af3f070.js | tr ',' '\n' | grep -iE 'href|src|iframe|portal|pricing|login|chunk' | head -20✓└{"content":[{"text":"(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[8974]\n\"PreloadChunks\"\n","type":"text"}]}
✓$ cd /sandbox/repo; timeout 25 curl -sS -o /dev/null -w "portal:%{http_code}\n" https://portal.ayedo.cloud; timeout 20 curl -sS -X POST -H 'Content-Type: application/json' -d '{}' -o /tmp/login.json -w …✓└{"content":[{"text":"portal:200\nlogin:400\n{\"type\":\"validation_error\",\"errors\":[{\"code\":\"required\",\"detail\":\"This field is required.\",\"attr\":\"…ctrl+o
cd /sandbox/repo; timeout 25 curl -sS -o /dev/null -w "portal:%{http_code}\n" https://portal.ayedo.cloud; timeout 20 curl -sS -X POST -H 'Content-Type: application/json' -d '{}' -o /tmp/login.json -w "login:%{http_code}\n" https://app.ayedo.cloud/api/login/; head -c 200 /tmp/login.json
{"content":[{"text":"portal:200\nlogin:400\n{\"type\":\"validation_error\",\"errors\":[{\"code\":\"required\",\"detail\":\"This field is required.\",\"attr\":\"username\"},{\"code\":\"required\",\"detail\":\"This field is required.\",\"attr\":\"password\"}]}","type":"text"}]}›Thinking…
The live API at app.ayedo.cloud responds: POST /api/login/ → 400 validation error (endpoint exists, public). Great — I can build a working example that includes a login path (username/password → token) and a token path.
Now write the example files. Keep it light: Python only (SDK already installed & verified).
Files:
README.md— quickstart + pricing + manageability + blockers.examples/ayedo_quickstart.py— reads POLYCRATE_BASE_URL, POLYCRATE_TOKEN or POLYCRATE_USERNAME/PASSWORD; uses SDK; lists workspaces; handles 401/403/network errors with clear messages.examples/pricing_snapshot.py— lists pricing products and cost statements (read-only) via SDK.requirements.txt— git pin to commit..gitignoreaddition for.env..env.example.
Then run:
python examples/ayedo_quickstart.pywith no creds → should print actionable message and exit 2.- with a dummy token → 401 message.
- with bogus base → connection error message.
- pricing_snapshot similarly.
Check SDK client signature: AuthenticatedClient(base_url=..., token=..., prefix="Bearer", verify_ssl=..., timeout=...). Let me check client.py for the fields and httpx timeout type. Also check polycrate.api.api.api_login_create signature (body model LoginRequest?).
Let me inspect.
✓$ cd /sandbox/repo; sed -n '1,60p' .venv/lib/python3.12/site-packages/polycrate/client.py; echo "=== login_create ==="; grep -nE "^def sync_detailed|body|_parse_response|status_code == " -A3 .venv/lib/p…✓└{"content":[{"text":"import ssl\nfrom typing import Any, Self\n\nimport httpx\nfrom attrs import define, evolve, field\n\n\n@define\nclass Client:\n \"\"\"A …ctrl+o
cd /sandbox/repo; sed -n '1,60p' .venv/lib/python3.12/site-packages/polycrate/client.py; echo "=== login_create ==="; grep -nE "^def sync_detailed|body|_parse_response|status_code == " -A3 .venv/lib/python3.12/site-packages/polycrate/api/api/api_login_create.py | head -40; echo "=== login models ==="; ls .venv/lib/python3.12/site-packages/polycrate/models/ | grep -iE 'login|token' | head
{"content":[{"text":"import ssl\nfrom typing import Any, Self\n\nimport httpx\nfrom attrs import define, evolve, field\n\n\n@define\nclass Client:\n \"\"\"A class for keeping track of data related to the API\n\n The following are accepted as keyword arguments and will be used to construct httpx Clients internally:\n\n ``base_url``: The base URL for the API, all requests are made to a relative path to this URL\n\n ``cookies``: A dictionary of cookies to be sent with every request\n\n ``headers``: A dictionary of headers to be sent with every request\n\n ``timeout``: The maximum amount of a time a request can take. API functions will raise\n httpx.TimeoutException if this is exceeded.\n\n ``verify_ssl``: Whether or not to verify the SSL certificate of the API server. This should be True in production,\n but can be set to False for testing purposes.\n\n ``follow_redirects``: Whether or not to follow redirects. Default value is False.\n\n ``httpx_args``: A dictionary of additional arguments to be passed to the ``httpx.Client`` and ``httpx.AsyncClient`` constructor.\n\n\n Attributes:\n raise_on_unexpected_status: Whether or not to raise an errors.UnexpectedStatus if the API returns a\n status code that was not documented in the source OpenAPI document. Can also be provided as a keyword\n argument to the constructor.\n \"\"\"\n\n raise_on_unexpected_status: bool = field(default=False, kw_only=True)\n _base_url: str = field(alias=\"base_url\")\n _cookies: dict[str, str] = field(factory=dict, kw_only=True, alias=\"cookies\")\n _headers: dict[str, str] = field(factory=dict, kw_only=True, alias=\"headers\")\n _timeout: httpx.Timeout | None = field(default=None, kw_only=True, alias=\"timeout\")\n _verify_ssl: str | bool | ssl.SSLContext = field(default=True, kw_only=True, alias=\"verify_ssl\")\n _follow_redirects: bool = field(default=False, kw_only=True, alias=\"follow_redirects\")\n _httpx_args: dict[str, Any] = field(factory=dict, kw_only=True, alias=\"httpx_args\")\n _client: httpx.Client | None = field(default=None, init=False)\n _async_client: httpx.AsyncClient | None = field(default=None, init=False)\n\n def with_headers(self, headers: dict[str, str]) -> \"Client\":\n \"\"\"Get a new client matching this one with additional headers\"\"\"\n if self._client is not None:\n self._client.headers.update(headers)\n if self._async_client is not None:\n self._async_client.headers.update(headers)\n return evolve(self, headers={**self._headers, **headers})\n\n def with_cookies(self, cookies: dict[str, str]) -> \"Client\":\n \"\"\"Get a new client matching this one with additional cookies\"\"\"\n if self._client is not None:\n self._client.cookies.update(cookies)\n if self._async_client is not None:\n=== login_create ===\n25: body: AuthRequest | AuthRequest | AuthRequest | Unset = UNSET,\n26-) -> dict[str, Any]:\n27- headers: dict[str, Any] = {}\n28-\n--\n34: if isinstance(body, AuthRequest):\n35: _kwargs[\"json\"] = body.to_dict()\n36-\n37- headers[\"Content-Type\"] = \"application/json\"\n38: if isinstance(body, AuthRequest):\n39: _kwargs[\"data\"] = body.to_dict()\n40- headers[\"Content-Type\"] = \"application/x-www-form-urlencoded\"\n41: if isinstance(body, AuthRequest):\n42: _kwargs[\"files\"] = body.to_multipart()\n43-\n44- headers[\"Content-Type\"] = \"multipart/form-data; boundary=+++\"\n45-\n--\n50:def _parse_response(\n51- *, client: AuthenticatedClient | Client, response: httpx.Response\n52-) -> (\n53- ApiLoginCreateValidationError\n--\n66: if response.status_code == 200:\n67- response_200 = Auth.from_dict(response.json())\n68-\n69- return response_200\n--\n71: if response.status_code == 400:\n72-\n73: def _parse_response_400(data: object) -> ApiLoginCreateValidationError | ParseErrorResponse:\n74- try:\n75- if not isinstance(data, dict):\n76- raise TypeError()\n--\n90: response_400 = _parse_response_400(response.json())\n91-\n92- return response_400\n93-\n94: if response.status_code == 404:\n=== login models ===\napi_login_create_non_field_errors_error_component.py\napi_login_create_non_field_errors_error_component_attr.py\napi_login_create_non_field_errors_error_component_code.py\napi_login_create_password_error_component.py\napi_login_create_password_error_component_attr.py\napi_login_create_password_error_component_code.py\napi_login_create_username_error_component.py\napi_login_create_username_error_component_attr.py\napi_login_create_username_error_component_code.py\napi_login_create_validation_error.py\n","type":"text"}]}›Thinking…
Good: AuthRequest, Auth models exist. Let me check Auth fields (token?) and AuthRequest fields.
Then write the code.
✓$ cd /sandbox/repo; for f in auth auth_request; do echo "== $f =="; grep -E "^ [a-z_]+:" .venv/lib/python3.12/site-packages/polycrate/models/$f.py | head -12; done; echo "== cost statement list param…✓└{"content":[{"text":"== auth ==\n username: str\n password: str\n additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n== au…ctrl+o
cd /sandbox/repo; for f in auth auth_request; do echo "== $f =="; grep -E "^ [a-z_]+:" .venv/lib/python3.12/site-packages/polycrate/models/$f.py | head -12; done; echo "== cost statement list params =="; grep -nE "^def sync_detailed" -A 25 .venv/lib/python3.12/site-packages/polycrate/api/api/api_v1_pricing_cost_statements_list.py | grep -E "^\s+[0-9]*[-:]\s+[a-z_]+:" | head -15; echo "== products list params =="; grep -nE "^def sync_detailed" -A 25 .venv/lib/python3.12/site-packages/polycrate/api/api/api_v1_pricing_products_list.py | grep -E "[a-z_]+: " | head -15
{"content":[{"text":"== auth ==\n username: str\n password: str\n additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n== auth_request ==\n username: str\n password: str\n additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)\n== cost statement list params ==\n== products list params ==\n241- client: AuthenticatedClient,\n242- archived: bool | Unset = UNSET,\n243- created_at: datetime.datetime | Unset = UNSET,\n244- created_by_component: ApiV1PricingProductsListCreatedByComponent | Unset = UNSET,\n245- debug_mode: bool | Unset = UNSET,\n246- kind: ApiV1PricingProductsListKind | Unset = UNSET,\n247- name: str | Unset = UNSET,\n248- ordering: str | Unset = UNSET,\n249- page: int | Unset = UNSET,\n250- page_size: int | Unset = UNSET,\n251- reconciliation_running: bool | Unset = UNSET,\n252- scope: ApiV1PricingProductsListScope | Unset = UNSET,\n253- search: str | Unset = UNSET,\n254- state: ApiV1PricingProductsListState | Unset = UNSET,\n255- updated_at: datetime.datetime | Unset = UNSET,\n","type":"text"}]}›Thinking…
The Auth model returned by login has username/password?? That looks like the generated model for the response is odd (it returns Auth with username/password fields — probably the OpenAPI response schema is the same AuthRequest). So the token may be in additional_properties. Hmm, docs say response includes token. So the generated model doesn't type it; token would land in additional_properties. Let me note that as a quirk: use resp.parsed.additional_properties.get("token") or fall back to raw JSON resp.content.
Let me check product_detail fields for pricing (price, currency, kind) and cost_statement_list has total_net, currency. Let's check product_detail fields quickly.
Then write files.
✓$ cd /sandbox/repo; grep -E "^ [a-z_]+:" .venv/lib/python3.12/site-packages/polycrate/models/product_detail.py | head -30; echo "== kind enum =="; grep -oE '"[a-z0-9-]+"' .venv/lib/python3.12/site-pa…✓└{"content":[{"text":" id: UUID\n created_at: datetime.datetime\n updated_at: datetime.datetime\n deleted_at: datetime.datetime | None\n is_delete…ctrl+o
cd /sandbox/repo; grep -E "^ [a-z_]+:" .venv/lib/python3.12/site-packages/polycrate/models/product_detail.py | head -30; echo "== kind enum =="; grep -oE '"[a-z0-9-]+"' .venv/lib/python3.12/site-packages/polycrate/models/product_detail_kind_enum.py 2>/dev/null | head -20 || ls .venv/lib/python3.12/site-packages/polycrate/models/ | grep -i "product_detail_kind" | head
{"content":[{"text":" id: UUID\n created_at: datetime.datetime\n updated_at: datetime.datetime\n deleted_at: datetime.datetime | None\n is_deleted: bool\n deleted_by_user: None | ProductDetailDeletedByUserType0\n state: LastStateEnum\n state_reason: None | str\n last_state: LastStateEnum\n last_state_change: datetime.datetime | None\n reconciliation_running: bool\n reconciliation_task_id: None | str\n reconciliation_task_meta: Any\n last_reconciliation: datetime.datetime | None\n discovery_enabled: bool\n discovery_running: bool\n discovery_task_id: None | str\n discovery_task_meta: Any\n last_discovery: datetime.datetime | None\n repair_running: bool\n repair_task_id: None | str\n repair_task_meta: Any\n last_repair: datetime.datetime | None\n scope: ScopeEnum\n kind: ProductKindEnum\n conditions: Any\n effective_criticality: EffectiveCriticalityEnum | None\n actual_availability: str\n organization: None | ProductDetailOrganizationType0\n workspace: None | ProductDetailWorkspaceType0\n== kind enum ==\n","type":"text"}]}›Thinking…
Enough model detail. Let me check ProductDetail for price field further down (name, price, currency, config).
Then write files. Let me grep more fields.
✓$ cd /sandbox/repo; grep -E "^ [a-z_]+:" .venv/lib/python3.12/site-packages/polycrate/models/product_detail.py | sed -n '30,60p'; echo "== ProductKindEnum =="; grep -oE '= "[a-z0-9-]+"' .venv/lib/pyt…✓└{"content":[{"text":" workspace: None | ProductDetailWorkspaceType0\n created: ProductDetailCreated\n url: str\n icon_url: str\n is_class_icon: b…ctrl+o
cd /sandbox/repo; grep -E "^ [a-z_]+:" .venv/lib/python3.12/site-packages/polycrate/models/product_detail.py | sed -n '30,60p'; echo "== ProductKindEnum =="; grep -oE '= "[a-z0-9-]+"' .venv/lib/python3.12/site-packages/polycrate/models/product_kind_enum.py | head -20
{"content":[{"text":" workspace: None | ProductDetailWorkspaceType0\n created: ProductDetailCreated\n url: str\n icon_url: str\n is_class_icon: bool\n effective_slo_target: float | None\n effective_sla_target: float | None\n last_action_run: None | ProductDetailLastActionRunType0\n name: str | Unset = UNSET\n display_name: None | str | Unset = UNSET\n labels: Any | Unset = UNSET\n annotations: Any | Unset = UNSET\n debug_mode: bool | Unset = UNSET\n provider: ProviderEnum | Unset = UNSET\n provider_reference: None | str | Unset = UNSET\n provider_id: None | str | Unset = UNSET\n reconciliation_enabled: bool | Unset = UNSET\n platform_service: bool | Unset = UNSET\n tolerations: Any | Unset = UNSET\n archived: bool | Unset = UNSET\n archived_at: datetime.datetime | None | Unset = UNSET\n archived_reason: None | str | Unset = UNSET\n criticality: BlankEnum | CriticalityEnum | None | Unset = UNSET\n target_availability: None | str | Unset = UNSET\n slo_target: None | str | Unset = UNSET\n slo_availability: str | Unset = UNSET\n sla_target: None | str | Unset = UNSET\n sla_availability: str | Unset = UNSET\n billing_interval: BillingIntervalEnum | Unset = UNSET\n price_per_unit: None | str | Unset = UNSET\n cost_per_unit: None | str | Unset = UNSET\n== ProductKindEnum ==\n","type":"text"}]}›Thinking…
Product has billing_interval, price_per_unit, cost_per_unit. Let me get BillingIntervalEnum values and ProductKindEnum values (grep pattern didn't match; check file content).
Then write the example. Let me check enums quickly.
✓$ cd /sandbox/repo; grep -E '^\s+[A-Z_0-9]+ = ' .venv/lib/python3.12/site-packages/polycrate/models/product_kind_enum.py .venv/lib/python3.12/site-packages/polycrate/models/billing_interval_enum.py | he…✓└{"content":[{"text":"(no output)","type":"text"}]}ctrl+o
cd /sandbox/repo; grep -E '^\s+[A-Z_0-9]+ = ' .venv/lib/python3.12/site-packages/polycrate/models/product_kind_enum.py .venv/lib/python3.12/site-packages/polycrate/models/billing_interval_enum.py | head -30
›Thinking…
Hmm, no output. Maybe the enums are named differently. Let me list model files containing "kind_enum" and "billing".
✓$ cd /sandbox/repo; ls .venv/lib/python3.12/site-packages/polycrate/models/ | grep -E 'kind_enum|billing' | head; echo "---"; grep -rn "class ProductKindEnum" .venv/lib/python3.12/site-packages/polycrat…✓└{"content":[{"text":"activity_kind_enum.py\nagent_kind_enum.py\nalert_kind_enum.py\nalert_router_kind_enum.py\napi_kind_enum.py\napi_v1_admin_users_create_is_bi…ctrl+o
cd /sandbox/repo; ls .venv/lib/python3.12/site-packages/polycrate/models/ | grep -E 'kind_enum|billing' | head; echo "---"; grep -rn "class ProductKindEnum" .venv/lib/python3.12/site-packages/polycrate/models/ | head -3
{"content":[{"text":"activity_kind_enum.py\nagent_kind_enum.py\nalert_kind_enum.py\nalert_router_kind_enum.py\napi_kind_enum.py\napi_v1_admin_users_create_is_billing_contact_error_component.py\napi_v1_admin_users_create_is_billing_contact_error_component_attr.py\napi_v1_admin_users_create_is_billing_contact_error_component_code.py\napi_v1_admin_users_partial_update_is_billing_contact_error_component.py\napi_v1_admin_users_partial_update_is_billing_contact_error_component_attr.py\n---\n","type":"text"}]}›Thinking…
ProductKindEnum is probably imported from a shared module. Let me grep for "product_kind" or find where it's defined. Quick: grep -rn "ProductKindEnum" .../models/__init__.py | head -3 and find file.
This is a detail; not critical for the example. Let me just find the values via a Python import.
✓$ cd /sandbox/repo && .venv/bin/python -c "✓└{"content":[{"text":" return getattr(self.__origin__, attr)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/usr/lib/python3.12/typing.py\", line 482, i…ctrl+o
cd /sandbox/repo && .venv/bin/python -c "
from polycrate.models import ProductKindEnum, BillingIntervalEnum
print('kinds:', [e.value for e in ProductKindEnum])
print('billing:', [e.value for e in BillingIntervalEnum])
" 2>&1 | tail -5
{"content":[{"text":" return getattr(self.__origin__, attr)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/usr/lib/python3.12/typing.py\", line 482, in __getattr__\n raise AttributeError(item)\nAttributeError: value\n","type":"text"}]}›Thinking…
The enums are Literal type aliases (openapi-python-client newer style uses Literal types). So ProductKindEnum is a typing Literal. Let me print the args.
✓$ cd /sandbox/repo && .venv/bin/python -c "✓└{"content":[{"text":"kinds: ('assistant', 'block-storage', 'dnszone', 'host', 'k8sapp', 'k8scluster', 'loadbalancer', 'object-storage', 'project', 'support')\nb…ctrl+o
cd /sandbox/repo && .venv/bin/python -c "
import typing
from polycrate.models import ProductKindEnum, BillingIntervalEnum
print('kinds:', typing.get_args(ProductKindEnum))
print('billing:', typing.get_args(BillingIntervalEnum))
"
{"content":[{"text":"kinds: ('assistant', 'block-storage', 'dnszone', 'host', 'k8sapp', 'k8scluster', 'loadbalancer', 'object-storage', 'project', 'support')\nbilling: ('day', 'hour', 'minute', 'month', 'second', 'year')\n","type":"text"}]}›Thinking…
Now I have solid facts. Let me write the deliverables.
Files:
README.mdexamples/quickstart_workspaces.pyexamples/pricing_report.pyrequirements.txt.env.example.gitignoreupdate (.env)
Design of quickstart script:
#!/usr/bin/env python3
"""Read-only ayedo Polycrate quickstart: list workspaces via the official Python SDK."""
from __future__ import annotations
import os, sys
import httpx
from polycrate import AuthenticatedClient, Client
from polycrate.api.api.api_v1_workspaces_list import sync_detailed as list_workspaces
from polycrate.api.api.api_login_create import sync_detailed as login
DEFAULT_BASE_URL = "https://app.ayedo.cloud"
def fail(msg, code=1): print(f"error: {msg}", file=sys.stderr); raise SystemExit(code)
def build_client(base_url, timeout=20.0):
token = os.environ.get("POLYCRATE_TOKEN")
if token: return AuthenticatedClient(base_url=base_url, token=token, prefix="Bearer", timeout=httpx.Timeout(timeout), verify_ssl=True)
user, pw = os.environ.get("POLYCRATE_USERNAME"), os.environ.get("POLYCRATE_PASSWORD")
if user and pw:
resp = login(client=Client(base_url=base_url, timeout=...), body=AuthRequest(username=user, password=pw))
if resp.status_code != 200: fail(...)
token = (getattr(resp.parsed, "additional_properties", {}) or {}).get("token") or json.loads(resp.content).get("token")
...
# no creds: anonymous probe
...
Handle: no creds → do an anonymous GET on /api/v1/workspaces/ via httpx through SDK's Client? The SDK's list function requires AuthenticatedClient per type hints but at runtime accepts Client. Actually sync_detailed(client=...) typed as AuthenticatedClient; passing Client works at runtime (duck typing). But cleaner: use httpx directly for the anonymous probe? Stay with SDK where possible; but for anonymous probe I can just pass a Client — let me test it works at runtime.
Let me write the scripts and then run tests:
- no creds → expect clear "need a token" message with 401 evidence from live API.
- POLYCRATE_TOKEN=dummy → 401 message.
- POLYCRATE_BASE_URL=https://nonexistent.invalid → connection error message.
- pricing_report.py same.
Let me write files now. Keep code clean and typed.
For the pricing report: list pricing products (page_size), show name, kind, billing_interval, price_per_unit; then list cost statements showing period, total_net, currency; handle 403 (staff-only) gracefully.
Check function signatures: api_v1_pricing_products_list.sync_detailed(client=..., page_size=..., kind=...). And cost_statements list params: let me check quickly what params exist (page/page_size). Let me grep.
Also model names for parsed results: PricingProductList? Let me check response model names: for products list → probably PaginatedProductListList or similar. Let me check quickly so I print correct attributes (count/results).
✓$ cd /sandbox/repo; for m in api_v1_pricing_products_list api_v1_pricing_cost_statements_list; do echo "== $m =="; grep -nE "^def sync_detailed" -A 8 .venv/lib/python3.12/site-packages/polycrate/api/api…✓└{"content":[{"text":"== api_v1_pricing_products_list ==\n239:def sync_detailed(\n240- *,\n241- client: AuthenticatedClient,\n242- archived: bool | Unse…ctrl+o
cd /sandbox/repo; for m in api_v1_pricing_products_list api_v1_pricing_cost_statements_list; do echo "== $m =="; grep -nE "^def sync_detailed" -A 8 .venv/lib/python3.12/site-packages/polycrate/api/api/$m.py | head -12; grep -nE "response_200 = " .venv/lib/python3.12/site-packages/polycrate/api/api/$m.py | head -2; done
{"content":[{"text":"== api_v1_pricing_products_list ==\n239:def sync_detailed(\n240- *,\n241- client: AuthenticatedClient,\n242- archived: bool | Unset = UNSET,\n243- created_at: datetime.datetime | Unset = UNSET,\n244- created_by_component: ApiV1PricingProductsListCreatedByComponent | Unset = UNSET,\n245- debug_mode: bool | Unset = UNSET,\n246- kind: ApiV1PricingProductsListKind | Unset = UNSET,\n247- name: str | Unset = UNSET,\n135: response_200 = PaginatedProductListList.from_dict(response.json())\n== api_v1_pricing_cost_statements_list ==\n250:def sync_detailed(\n251- *,\n252- client: AuthenticatedClient,\n253- archived: bool | Unset = UNSET,\n254- created_at: datetime.datetime | Unset = UNSET,\n255- created_by_component: ApiV1PricingCostStatementsListCreatedByComponent | Unset = UNSET,\n256- debug_mode: bool | Unset = UNSET,\n257- kind: ApiV1PricingCostStatementsListKind | Unset = UNSET,\n258- name: str | Unset = UNSET,\n140: response_200 = PaginatedCostStatementListList.from_dict(response.json())\n","type":"text"}]}›Thinking…
Paginated wrappers have count/results presumably. Let me confirm fields: PaginatedProductListList has count, results, next, previous? And ProductList has name, kind, billing_interval, price_per_unit?
Quick check then write.
✓$ cd /sandbox/repo && .venv/bin/python -c "✓└{"content":[{"text":"PaginatedProductListList ['count', 'results', 'previous']\nProductList ['name', 'organization', 'kind', 'billing_interval', 'price_per_unit…ctrl+o
cd /sandbox/repo && .venv/bin/python -c "
import attrs
from polycrate.models import PaginatedProductListList, ProductList, PaginatedCostStatementListList, CostStatementList
for m in (PaginatedProductListList, ProductList, CostStatementList):
print(m.__name__, [f.name for f in attrs.fields(m) if f.name in ('count','results','next','previous','name','kind','billing_interval','price_per_unit','currency','total_net','period_start','period_end','organization','display_name')])
"
{"content":[{"text":"PaginatedProductListList ['count', 'results', 'previous']\nProductList ['name', 'organization', 'kind', 'billing_interval', 'price_per_unit']\nCostStatementList ['name', 'organization', 'period_start', 'period_end', 'total_net', 'currency']\n","type":"text"}]}AX Fundamentals
23 checks. Open an area for details.
This check is a quick preview. Test more tasks and models with Gauge Agents.
Test your product with Gauge Agents