Skip to content

feat(org): fold custom domains into iblai-api-org - #23

Open
mamigot wants to merge 1 commit into
mainfrom
feat/org-custom-domains
Open

feat(org): fold custom domains into iblai-api-org#23
mamigot wants to merge 1 commit into
mainfrom
feat/org-custom-domains

Conversation

@mamigot

@mamigot mamigot commented Jul 24, 2026

Copy link
Copy Markdown
Member

What

Adds the /api/custom-domains/ surface to /iblai-api-org: list an org's vanity hostnames, attach one, repoint it at a different SPA (by id or by hostname), soft-delete/restore, and hard delete. Six endpoints, previously uncovered anywhere in the repo.

Why fold in rather than add a skill

Six endpoints configuring one org-wide concern. A standalone /iblai-api-custom-domain would be a very thin skill sitting next to /iblai-api-org, and the two would be reached for in the same breath ("set this org up"). So the skill now carries two surfaces — org settings and custom domains — and splits Reads / Writes with ### subheadings per the multi-resource convention in CLAUDE.md. Reads/Writes stays the top-level split.

Sourcing

Per CLAUDE.md ("source of truth = the repo URLconf, not the docs"), everything is read off iblai-dm-prodl_iblai_services_app/{urls.py, views/custom_domain.py, models/custom_domain.py}.

This mattered more here than usual: the published OpenAPI schema documents all six paths with "No response body" and no request schema whatsoever. It could not have produced a single field name. Everything below came from the view and model:

  • platform_key + custom_domain required on create; spa / is_deleted optional
  • the spa enum — auth · skillsai · mentorai · analyticsai, default auth (wire values, kept verbatim per the terminology rule)
  • GET /api/custom-domains/ is public — no authentication or permission checks at all. Every write requires an admin token. Worth knowing before wiring it up.
  • it requires either platform_key or domain; neither is a 400
  • ?include_deleted and the ?status= truthy/falsy vocabulary filtering on registered_with_dns_pro
  • an org with no domains returns {}, not an empty list — an easy client crash
  • hostnames are globally unique, so "Domain already exists" may mean another org holds it
  • soft delete (deleted-status/, reversible) vs hard delete (delete/, permanent) — genuinely different operations behind similar-looking URLs
  • registered_with_dns_pro is non-editable, set by a backend signal; instructions carries the DNS setup steps

Notable behavior worth a reviewer's eye

The public list endpoint is the one I'd want confirmed as intentional. @authentication_classes([]) + @permission_classes([]) on get_custom_domains means anyone can enumerate an org's domains, and the response embeds platform_metadata. I documented it as-is because that's what the code does and callers need to know they can hit it pre-login — but if it's meant to be scoped, the skill should be corrected alongside the fix rather than describing the current behavior.

Also: {domain_id}/status/ and {domain_id}/deleted-status/ use CustomDomainOwnerPermission while by-name/{domain_name}/status/ and create/ use IsPlatformAdmin. I've described both as requiring admin, which is right for the latter two; if owner-permission is meaningfully broader than admin for the id-addressed routes, that nuance is worth adding.

Also

  • README: /iblai-api-org row now mentions custom domains.

🤖 Generated with Claude Code

Adds the /api/custom-domains/ surface to the org skill: list an org's vanity
hostnames, attach one, repoint it at a different SPA (by id or by hostname),
soft-delete/restore, and hard delete.

It belongs here rather than in its own skill — six endpoints configuring one
org-wide concern, alongside the other org-wide settings — so the skill now
carries two surfaces and splits Reads/Writes with ### subheadings per the
multi-resource convention in CLAUDE.md.

Endpoints, required fields, permissions, enum values, and response shapes are
read off the iblai-dm-pro URLconf, view, and model (dl_iblai_services_app
custom_domain), per "source of truth = the repo URLconf". The published
OpenAPI schema documents these paths with no request or response bodies at
all, so it could not have produced the field names.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant