Required — org-api forces at least one CONTROL person for
RouteFusion-enabled customers (Phase 1), so create() can be called
with relatedPeople from any construction site. Required (not
optional-and-asserted-at-use) specifically so the compiler enumerates
every construction site when this dependency is added, rather than a
caller finding out at runtime — after the org row + AiPrise verify()
have already committed — that nobody wired it in. Every consumer
package constructs a small OrganizationQueryService to hand to its
own RelatedPersonService instance (avoids a circular construction
against this class) — see organization-api/src/main.ts for the
canonical shape.
OptionalisMainOrg?: booleanMark this as the customer's PRIMARY organization
(organization.is_main_org) — the one every customer-scoped read resolves
to, and the replacement for the old customer.organization_id pointer.
Exactly one per customer, enforced by the partial unique index
organization_main_org_per_customer_idx; a second one surfaces as
DUPLICATE through the existing unique-violation catch. Set only by the
customer-onboarding flow (@cfxlabsinc/onboarding-services).
OptionalrelatedPeople?: Omit<Control persons / beneficial owners to create against this org right
after it's inserted (organization-api forces at least one CONTROL
person when the customer is RouteFusion-enabled — see
OrganizationController). Each is forwarded to relatedPersonService
the same way the standalone related-person endpoint does.
OptionalverificationProfileId?: stringAn existing AiPrise business profile to attach to this org. When supplied
the profile is FETCHED here and its current result/session are attached
to the org INSTEAD of kicking off a fresh async verify(). The
customer-onboarding flow pastes a profile the operator already validated,
and no AiPrise webhook will ever fire for it — so without this the org
would sit PENDING_VERIFICATION forever.
Only the id is accepted: the verification outcome is always read from AiPrise (the source of truth), never trusted from the caller.
Resolve the organization's incorporation (formation) date from its AiPrise
business profile, for prefilling the RouteFusion virtual-account creation
form (RouteFusion's createBusinessEntity requires incorporation_date).
The date is not stored on the org record — it is read from AiPrise's
registration_info.formation_date (falling back to the profile's top-level
formation_date). Absence of a verification profile or a formation date is
not an error: the admin can enter the value manually, so this returns
{ incorporationDate: null } in those cases.
Organization external id
The URL to redirect to, after verification is completed
URL to start an identity document verification session
Bust every cached page — consumer or admin — that a write touching
customerIds could have affected.
The collection tag is always cleared alongside, because an unscoped admin page carries only that tag and no customer id can reach it.
keys deletes exact entries in addition to the tag surgery, passing the
key arguments — this cache hashes and prefixes them. It exists because
invalidateTag resolves keys through the FT index, which does not exist in
memory mode (valkeyClient: null); there the tag bust is a total no-op, and
an exact-key delete is the only thing that keeps read-after-write honest.
Services that folded get onto search pass that get's arguments.
These deletes only reach this instance's own key space, because the key space is partitioned per surface. That is sufficient, and a writer must not try to name the other surface's key:
get entry is itself tagged (by customer
id, or by the collection tag when unscoped), so the tag bust above already
evicts it from the shared L2 and publishes the peer eviction.OptionalcountriesOfIncorporation?: string[]OptionalcreatedAt?: DbTimestampCriteriaOptionaldescriptionLike?: stringPartial match on description
Optionalids?: string[]OptionalincludeDeleted?: booleanOptionallegalEntityTypeLike?: stringPartial match on legal entity type
OptionallegalEntityTypes?: (Exact match to these canonical legal-entity-type codes (US structures).
OptionalnameLike?: stringPartial match on legal entity name
OptionalorderBy?: DbOrderByCriterion<Optionalpage?: numberDefaults to 1
OptionalpageSize?: numberDefaults to 50
OptionalreferenceIdLike?: stringPartial match on reference ID
OptionalreferenceIds?: string[]OptionalrouteFusionEntityIds?: string[]Matches the pinned RouteFusion business-entity id on
data->'routeFusionEntity'->>'entityId'. Mirrors the admin-side filter so
Service/AdminService search signatures stay aligned.
Optionalstatuses?: ("ACTIVE" | "DELETED" | "DISABLED" | "PENDING_VERIFICATION")[]OptionalupdatedAt?: DbTimestampCriteriaProtectedsearchTags to write on a page, derived from the scope the query searched — never from the customers present in the result.
That distinction is load-bearing. An entry tagged with the customer ids in
the page carries no tags when the page is empty, so nothing can ever bust
it and a later create leaves it stale for the full L2 TTL. Tagging by
scope means an empty page still carries the tag for what it searched.
An unscoped read falls back to the collection tag, since no customer-id tag can reach a row whose owner did not exist when the entry was written.
StaticadminThe exact key OrganizationAdminQueryService.get reads through, for its
includeDeleted: false callers.
The includeDeleted: true variant hashes differently and is reached only by
the collection tag, which every Valkey-backed environment has. That gap is
deliberate and pre-existing: it only matters in memory mode, where no admin
get runs with includeDeleted: true.
StaticconsumerThe exact key OrganizationQueryService.get reads through.
get folds onto search, so these args must stay identical to the ones it
passes ({ customerId, ids: [id], pageSize: 1 }) plus the defaults search
bakes into its own key — includeDeleted: false and page: 1. Every other
filter is undefined there, and serviceCacheKey canonicalizes with JCS,
which drops undefined properties. organizationCache.test.ts pins the
equivalence.
StaticdeserializeThe inverse of serializeOrganization: rehydrates the Date fields.
StaticserializeA search row → its JSON-safe form. Safe to call without a cache instance.
Unless need to directly act on an organization specifically, you should be using
EntityServiceinstead.The shared search cache is rooted at OrganizationCache, so this writer inherits it and self-busts via
invalidateCache— one call that reaches both the customer and admin surfaces, since they share a namespace and a tag vocabulary.