IDs & External References
Master the dual ID system providing both database performance and flexible integration with your external systems.
For HTTP/REST users: Use native IDs (BizEntityID) for performance, external IDs (ExternalBizEntityId) for client system integration
For .NET SDK users: Use native IDs for queries, external IDs for creating/updating resources from external systems
Both approaches support the dual ID system - every resource has a native database ID (integer) and an optional external ID (string) for your reference.
Quick Reference
Dual ID System
Native IDs (Database Primary Keys)
Type: Integer (int, long)
Format: Auto-generated by database
Example: BizEntityID, AccountID, PersonID
Performance: Indexed integers (fastest queries)
Use for: Internal operations, fast lookups, performance-critical queries
External IDs (Client-Controlled References)
Type: String (max 50 characters)
Format: Your choice (alphanumeric + hyphens recommended)
Example: ExternalBizEntityId, ExternalAccountId, ExternalPersonId
Performance: String lookups (slower than native IDs)
Use for: Integration with external systems, cross-system references, deduplication
Critical: ExternalPersonId Pattern
ExternalPersonId prevents duplicate person records - same ExternalPersonId reuses existing person instead of creating duplicate.
Essential for preventing John Smith from your CRM creating 5 duplicate person records across different entities.
Use native IDs for performance, external IDs for integration - both are supported across all endpoints.
Why This Matters
The dual ID system enables seamless integration with external systems while maintaining database performance through optimized integer indexes. ExternalPersonId deduplication prevents duplicate person records across operations, essential for registry platforms synchronizing data from multiple sources.
The Dual ID Principle
Every resource in the 7G API supports two parallel identification systems: native database IDs (auto-generated integers) and external IDs (client-controlled strings). You can use either or both depending on your integration needs.
How It Works:
- Create resource - Optionally provide ExternalBizEntityId (your reference)
- API assigns native ID - Database generates BizEntityID (integer primary key)
- Response includes both - API returns both BizEntityID and ExternalBizEntityId
- Query by either - Use native ID for performance, external ID for integration
- Uniqueness enforced - Each external ID is unique per client (case-insensitive)
Native IDs vs External IDs
Understanding the dual identification system
Native IDs (Database Primary Keys)
Characteristics:
- Auto-generated by database (identity columns)
- Integer type (
intfor most resources,longfor high-volume like transactions) - Guaranteed unique globally
- Never null for existing records
- Database-indexed for optimal performance
Naming Pattern: *ID (uppercase 'ID')
| Resource | Native ID Field | Type |
|---|---|---|
| BizEntity | BizEntityID |
int |
| Account | AccountID |
int |
| Person | PersonID |
int |
| Organisation | OrganisationID |
int |
| Investment | InvestmentID |
int |
| BizTransaction | BizTransactionID |
long |
| Distribution | DistributionID |
int |
# Query by native ID (fastest)
GET /BizEntity?BizEntityID=1001
# Response includes the native ID
{
"result": true,
"data": [{
"bizEntityID": 1001,
"name": "Smith Super Fund",
"bizEntityTypeID": 4
}]
}External IDs (Client-Controlled References)
Characteristics:
- Client-defined strings (your choice of format)
- Maximum 50 characters
- Case-insensitive matching
- Unique per client (not globally)
- Optional (can be null)
Naming Pattern: External*Id (titlecase 'd')
| Resource | External ID Field | Max Length |
|---|---|---|
| BizEntity | ExternalBizEntityId |
50 chars |
| Account | ExternalAccountId |
50 chars |
| Person | ExternalPersonId |
50 chars |
| Organisation | ExternalOrganisationId |
50 chars |
| Investment | ExternalInvestmentId |
50 chars |
| Product | ExternalProductId |
50 chars |
Format Recommendations:
- Alphanumeric with hyphens:
CLIENT-001,ACC-7G-12345 - Avoid special characters, spaces, or symbols
- Consistent naming convention across your integration
- Maximum 50 characters
# Query by external ID
GET /BizEntity?ExternalBizEntityId=CLIENT-001
# Response includes both IDs
{
"result": true,
"data": [{
"bizEntityID": 1001,
"externalBizEntityId": "CLIENT-001",
"name": "Smith Family SMSF"
}]
}
# Create with external ID
POST /BizEntity
{
"externalBizEntityId": "CLIENT-001",
"name": "Smith Family SMSF",
"bizEntityTypeID": 4
}Conditional ID Pairs
When native and external IDs are alternatives - one of the two is required
Many 7G endpoints treat the native ID and its external counterpart as a conditional pair: either identifier is accepted, but at least one must be supplied. This lets native integrators work with database IDs while external integrators reference resources by their own identifiers - without requiring both on every call.
The Rule
For a conditional pair (e.g. ProductID / ExternalProductId),
provide exactly one. Supplying both is valid - the native ID takes precedence and the external ID
is treated as a correlation reference.
Distribution Queries Also Enforce the Pair
Unlike most GET filters (where every field is optional),
GET /Distribution and DELETE /Distribution require at least
ProductID or ExternalProductId - the controller rejects the request
otherwise. This is a deliberate scope guard against unbounded distribution queries.
Performance Comparison
Choose the right ID type for your use case
| Aspect | Native IDs | External IDs |
|---|---|---|
| Query Speed | Sub-second (indexed integers) | Slower (string comparison) |
| Storage | 4 bytes (int), 8 bytes (long) | Variable (up to 50 chars) |
| Uniqueness | Global (database-enforced) | Per-client (case-insensitive) |
| Use Case | Internal operations, fast lookups | Cross-system integration |
| Recommended For | Performance-critical queries | External system references |
Performance
Recommendation: Use native IDs for performance-critical queries, store both IDs in your local database for fast bidirectional lookups.
ExternalPersonId Deduplication Pattern
Prevent duplicate person records across operations
Critical Feature
When creating or updating resources that reference a Person, the same ExternalPersonId across multiple operations reuses the existing person record instead of creating duplicates.
The Problem Without Deduplication:
- Create BizEntity 1 with person "John Smith" (ExternalPersonId: "PERSON-001") → Creates PersonID 501
- Create BizEntity 2 with same person (ExternalPersonId: "PERSON-001")
- WITHOUT deduplication: Creates PersonID 502 (DUPLICATE!)
- WITH deduplication: Reuses PersonID 501 (CORRECT!)
How It Works:
- First operation creates person with
ExternalPersonId: "PERSON-001"→ AssignedPersonID: 501 - Second operation references
ExternalPersonId: "PERSON-001"→ API finds existingPersonID: 501 - Second entity links to existing person (no duplicate created)
# Entity 1 - Creates new person
POST /BizEntity
{
"name": "Smith Family SMSF",
"bizEntityTypeID": 3,
"person": {
"firstName": "John",
"lastName": "Smith",
"externalPersonId": "PERSON-JohnSmith-001"
}
}
# API creates PersonID 501
# Entity 2 - Reuses same person (same ExternalPersonId)
POST /BizEntity
{
"name": "Smith Investment Trust",
"bizEntityTypeID": 5,
"person": {
"firstName": "John",
"lastName": "Smith",
"externalPersonId": "PERSON-JohnSmith-001"
}
}
# API reuses existing PersonID 501 (no duplicate created)Best Practice: Always provide ExternalPersonId when creating entities with person references to prevent duplicate person records.
BizEntity Advanced Scenarios: For placement rules across nested contexts (bizEntityParties, associatedPersons, contact), advanced multi-role patterns (SMSF, Trust), and validation details, see Person Reuse & Deduplication Logic in the BizEntity reference.
Context-Dependent External ID Meaning
The same External ID field means different things depending on where it appears
In nested structures like BizEntityParty, External IDs serve dual purposes.
The same ExternalOrganisationId field triggers completely different behaviour
depending on whether it's placed at the party level or inside a
nested Organisation object.
| Where It Appears | What It Means | What Happens |
|---|---|---|
BizEntityParty.ExternalOrganisationId |
"Find this existing org and link it" | Lookup only - constraint error if not found |
BizEntityParty.Organisation.ExternalOrganisationId |
"This org's identity for deduplication" | Creates if new, updates if found, then links |
{
"bizEntityParties": [{
"externalOrganisationId": "ORG-001",
"organisation": null
}]
}
// ExternalOrganisationId at PARTY level
// → Triggers lookup-only path
// → Finds existing org "ORG-001" and links it
// → Constraint error if "ORG-001" does not existCommon Mistake
Setting ExternalOrganisationId at the party level for an organisation that
doesn't exist yet triggers the lookup-only path. The nested Organisation object - even if
provided - is never reached because the party-level ID takes priority. The result is a
constraint error: "Either Person or Organisation must be assigned to BizEntityParty to satisfy constraint."
Fix: Set all party-level IDs to null and provide a nested
Organisation object. The nested object's ExternalOrganisationId handles
creation or update automatically.
The same logic applies identically for ExternalPersonId.
For full JSON examples of all three patterns (reference, create, update), see:
Investments Have a Third Identifier: SecurityCode
Native ID, external ID, and a business-facing security code - three ways to identify the same investment
Investment resources support a third identifier beyond the standard native/external pair:
SecurityCode. It's a business-facing, ticker-style code (e.g. HGF001A, GRW001)
that your team already uses to refer to investment classes. Every endpoint that resolves an investment -
filters, transaction payloads, event payloads, distribution payloads - accepts any of the three.
InvestmentID
Type: int
Scope: Global (system-assigned)
Use for: Fastest lookups, internal system references
ExternalInvestmentId
Type: string? (max 50)
Scope: Instance-unique (your reference)
Use for: Cross-system integration, idempotent writes
SecurityCode
Type: string?
Scope: Unique within a product
Use for: Business-facing operations, ticker-style references
SecurityCode is Product-Scoped - Supply a Product Too
SecurityCode + ProductID is the unique key on the Investment table.
The same code (e.g. HGF001A) can legitimately exist in multiple products. When you use
SecurityCode as an identifier on any endpoint, you must also supply
ProductID or ExternalProductId so the API can resolve it unambiguously.
Creating an Investment with a SecurityCode that already exists in the same product returns: "SecurityCode already exist, Please try again with a different value."
Resolution Precedence
When a caller provides more than one identifier, the API's resolver tries each branch in a single OR'd query: native ID first, external ID next, SecurityCode last. If the identifiers don't all point to the same record, results are indeterminate. Supply exactly one identifier per operation to avoid ambiguity.
The Precedence Hint
When a lookup fails and the caller supplied both a primary ID and a SecurityCode, the API returns:
"No records were found against provided information, Note: InvestmentID/ExternalInvestmentId prioritized over SecurityCode as multiple values were provided!"
This is an informational disambiguation hint, not a strict precedence rule.
Where SecurityCode Works - And Where It Doesn't
SecurityCode is accepted as an identifier on most operations that reference an investment, but there are three deliberate exclusions documented below. Consult the relevant endpoint page for the exact parameter name, casing, and example payload.
| Operation | SecurityCode Accepted? | Notes |
|---|---|---|
GET /Investment, GET /BizEntity, GET /BizTransaction,
GET /BizEntity/Account/Holdings, GET /Investment/Price,
GET /Distribution |
✅ Yes | Filter parameter. Shape mirrors the sibling ExternalInvestmentId field on each filter (plain string? on some, FilterField<string?> on others). |
All POST /BizTransaction/* bodies (Deposit, Redemption, Transfer, AdjustQuantity, SetQuantity, Conversion) |
✅ Yes | Body identifier. Must be accompanied by ProductID or ExternalProductId. |
POST /BizEvent/NoticeOfDeposit, POST /BizEvent/NoticeOfRedemption,
POST /BizEvent/NoticeOfConversion |
✅ Yes | Body identifier. Conversion uses fromSecurityCode and toSecurityCode. |
POST /Distribution, PUT /Distribution |
✅ Yes | Supports both securityCode (the distributing investment) and drpSecurityCode (the DRP target investment). |
POST /Investment |
✅ Yes | Creation field. Uniqueness enforced against the target ProductID. |
POST /BizTransaction/Allotment |
⚠️ Present but ignored | The DTO carries SecurityCode for symmetry, but the service resolves the investment from the parent BizEvent (type To_Be_Allotted, status Pending). Same applies to InvestmentID and ExternalInvestmentId on this payload. |
PUT /Investment |
⚠️ Updatable, not an identifier | You can update an investment's SecurityCode via the payload, but the record itself must be identified by InvestmentID or ExternalInvestmentId. SecurityCode alone is rejected. |
DELETE /Investment |
❌ No | Controller binds only InvestmentID and ExternalInvestmentId; rejects requests missing both with "Either InvestmentID or ExternalInvestmentId must be provided!". |
DELETE /Investment/Price |
❌ No | Same bound query parameters — InvestmentID or ExternalInvestmentId. Note that this endpoint's controller does not pre-validate that at least one identifier is supplied, so an empty call is passed through to the service rather than returning the verbatim "Either…" message. |
Exact Error Strings
When a SecurityCode can't be resolved, the API returns a specific message. Match against these exact strings
rather than a substring - several variants differ only in phrasing or the presence of the ProductID hint.
| Context | Message |
|---|---|
| BizTransaction / BizEvent / Distribution primary investment lookup | "No record was found for the provided InvestmentID, ExternalInvestmentId or SecurityCode in ProductID ({id})." |
| Conversion - From side | "No record was found for the provided FromInvestmentID, FromExternalInvestmentId or FromSecurityCode in ProductID ({id})." |
| Conversion - To side | "No record was found for the provided ToInvestmentID, ToExternalInvestmentId or ToSecurityCode in ProductID ({id})." |
| Distribution DRP target | "Can't find any record with the provided DrpInvestmentID, DrpExternalInvestmentId or DrpSecurityCode." |
| BizEntity filter (instance-scoped, no product hint) | "No record was found for the provided InvestmentID, ExternalInvestmentId or SecurityCode." |
| Investment create/update conflict | "SecurityCode already exist, Please try again with a different value." |
# Filter investments by SecurityCode (product-scoped)
GET /investment?SecurityCode=HGF001A&ProductID=100
# Deposit against an investment identified by SecurityCode
POST /BizTransaction/Deposit
{
"productID": 100,
"accountID": 5001,
"securityCode": "HGF001A",
"amount": 10000.00,
"paymentMethodID": 1,
"transactionDate": "2026-04-22"
}
# Conversion uses From/To prefixed variants
POST /BizTransaction/Conversion
{
"productID": 100,
"accountID": 5001,
"fromSecurityCode": "GRA001",
"toSecurityCode": "GRB001",
"quantity": 1000,
"transactionDate": "2026-04-22"
}
# Distribution supports drpSecurityCode for the DRP target
POST /Distribution
{
"productID": 100,
"securityCode": "GRW001",
"drpSecurityCode": "DRP001",
"accrualDate": "2026-04-01",
"recordDate": "2026-04-15",
"paymentDate": "2026-04-30"
}Conversion and DRP use prefixed variants:
FromSecurityCode/ToSecurityCode on Conversion payloads,
DrpSecurityCode on Distribution payloads. The rules above apply identically - each is
resolved within the same ProductID scope as its sibling native/external pair.
Filtering by IDs
Query by native IDs, external IDs, or both simultaneously
You can filter by native IDs, external IDs, or both simultaneously.
Filtering by Native ID:
# BizEntityID on BizEntityFilter is a plain scalar (exact match only)
GET /BizEntity?BizEntityID=1001
# For operator-rich queries, use a FilterField property like BizEntityTypeID
GET /BizEntity?BizEntityTypeID.equal=3
GET /BizEntity?BizEntityTypeID.in=1,2,3Filtering by External ID:
# ExternalBizEntityId on BizEntityFilter is a plain string (exact match only)
GET /BizEntity?ExternalBizEntityId=CLIENT-001
# For operator-rich external-ID queries, use a FilterField property such as
# ExternalAccountId (FilterField<string?>) on BizEntityFilter.
GET /BizEntity?ExternalAccountId.beginsWith=CLIENT-
# Combine filters (type + account external-ID prefix)
GET /BizEntity?BizEntityTypeID.equal=3&ExternalAccountId.beginsWith=SMSF-See Query & Filtering for all filter operators including beginsWith (HTTP) / StartsWith (SDK), contains / Contains, in / In.
Best Practices
Guidelines for effective ID management
Recommended
- Use native IDs for performance-critical queries and relationships
- Provide external IDs when creating resources from external systems
- Always use ExternalPersonId to prevent duplicate person records
- Store both IDs in your local database for bidirectional lookups
- Use consistent external ID format across your integration (e.g.,
CLIENT-{`{id}`})
Avoid
- Relying solely on external IDs for high-frequency production queries (performance impact)
- Using special characters, spaces, or symbols in external IDs (alphanumeric + hyphens only)
- Exceeding the 50-character limit for external IDs
- Creating person records without ExternalPersonId (causes duplicates)
- Assuming external IDs are case-sensitive (they're matched case-insensitively)