Skip to content

feat: add exact name filter to GET /api/organizations - #447

Merged
hieptl merged 3 commits into
mainfrom
hieptl/ohe-3156
Sep 22, 2026
Merged

hieptl merged 3 commits into
mainfrom
hieptl/ohe-3156

Conversation

@hieptl

@hieptl hieptl commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

HUMAN:

  • A human has tested these changes.

AGENT:


Why

BuildOne provisions and manages orgs programmatically and needs to resolve an org by its human-readable name to wire up their provisioning and routing flows. Today the only way to fetch an org through the API is by UUID (GET /api/organizations/{org_id}), so they would have to hardcode UUIDs. GET /api/organizations already lists the caller's orgs but has no way to narrow the result, so this adds an exact name filter to that endpoint instead of introducing a new route.

Summary

  • GET /api/organizations accepts an optional name query parameter (1-255 chars). When set, only the caller's member org with exactly that name is returned. The match is exact and case-sensitive, and it is applied on top of the existing membership join, so a name the caller is not a member of returns an empty page instead of revealing that the org exists. ?name= returns 422. Leaving name out keeps the current behavior.
  • name is passed through OrgService.get_user_orgs_paginated to OrgStore.get_user_orgs_paginated, which adds Org.name == name to the query before offset/limit.
  • Unit tests for the store (exact match, case sensitivity, orgs the caller is not a member of), the service (filter reaches the store), and the route (filter forwarded to the service, empty name rejected).

Issue Number

Linear: https://linear.app/all-hands-ai/issue/OHE-3156

How to Test

Automated (all run locally):

cd enterprise && poetry run pytest \
  tests/unit/test_org_store.py \
  tests/unit/test_org_service.py \
  tests/unit/server/routes/test_orgs.py -q
# 232 passed, 3 skipped

uvx ruff@0.12.5 check --config enterprise/dev_config/python/ruff.toml <changed files>
uvx ruff@0.12.5 format --check --config enterprise/dev_config/python/ruff.toml <changed files>
# All checks passed! / 6 files already formatted

MYPYPATH=enterprise enterprise/.venv/bin/mypy \
  --config-file enterprise/dev_config/python/mypy.ini -p server -p storage
# 0 errors in the changed files (20 pre-existing errors elsewhere)

Manual, against a running deployment (with a session cookie or an API key):

  1. GET /api/organizations?name=<exact name of an org you belong to>: expect 200 with that single org in items and next_page_id: null.
  2. Same request with different casing (e.g. acme for Acme): expect 200 with an empty items.
  3. GET /api/organizations?name=<name of an existing org you are not a member of>: expect 200 with an empty items.
  4. GET /api/organizations?name=: expect 422.
  5. GET /api/organizations without name: expect the same list as before this change.

Video/Screenshots

N/A.

Type

  • Bug fix
  • Feature
  • Refactor
  • Breaking change
  • Docs / chore

Notes

  • No migration or config change. The new parameter is optional, so existing callers are unaffected.
  • org.name has a unique constraint, so a name lookup returns at most one org. The existing OrgPage shape is kept so clients don't need a new response model.
  • Only exact matching is supported. Substring or case-insensitive search can be a follow-up if BuildOne needs it.
  • The lookup only covers orgs the caller belongs to. POST /api/organizations does not add the creator as a member, so an account that creates orgs through that endpoint won't find them by name unless it is also a member. If BuildOne's provisioning account works that way, we'll need a follow-up lookup for super-role callers.

Enterprise server image for this PR:

ghcr.io/openhands/enterprise-server:sha-1bcb3fb

Organizations can currently only be fetched through the API by UUID, so
integrations that provision orgs programmatically cannot resolve an org
from its human-readable name without hardcoding IDs (OHE-3156).

Add an optional `name` query parameter to GET /api/organizations that
narrows the listing to the caller's member organization with exactly that
name. The match is exact and case-sensitive and is applied on top of the
existing membership join, so a name the caller is not a member of yields
an empty page instead of revealing that the org exists. An empty name is
rejected with 422, and omitting the parameter keeps the current behavior.

The filter is passed through OrgService.get_user_orgs_paginated and
OrgStore.get_user_orgs_paginated, with tests at the store, service and
route levels.
@github-actions github-actions Bot added the type: feat A new feature label Sep 21, 2026
@hieptl hieptl self-assigned this Sep 21, 2026
@hieptl
hieptl marked this pull request as ready for review September 21, 2026 10:43
@github-actions

Copy link
Copy Markdown

Coverage report

Click to see where and how coverage changed

FileStatementsMissingCoverageCoverage
(new stmts)
Lines missing
  storage
  org_service.py
  org_store.py
Project Total  

This report was generated by python-coverage-comment-action

@tofarr tofarr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are following the pattern we use elsewhere in the app, we would use name__eq as the argument name - but I see we already have a user_id

@hieptl
hieptl merged commit 8e1ceef into main Sep 22, 2026
18 checks passed
@hieptl
hieptl deleted the hieptl/ohe-3156 branch September 22, 2026 14:09
@tofarr

tofarr commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

🍰

@openhands-release-bot openhands-release-bot Bot added the released: 1.63.0 Shipped in 1.63.0 label Sep 22, 2026
@openhands-release-bot

Copy link
Copy Markdown
Contributor

🚀 Released in 1.63.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

released: 1.63.0 Shipped in 1.63.0 type: feat A new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants