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:

  1. Create resource - Optionally provide ExternalBizEntityId (your reference)
  2. API assigns native ID - Database generates BizEntityID (integer primary key)
  3. Response includes both - API returns both BizEntityID and ExternalBizEntityId
  4. Query by either - Use native ID for performance, external ID for integration
  5. 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 (int for most resources, long for 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
json
# 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
json
# 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:

  1. First operation creates person with ExternalPersonId: "PERSON-001" → Assigned PersonID: 501
  2. Second operation references ExternalPersonId: "PERSON-001" → API finds existing PersonID: 501
  3. Second entity links to existing person (no duplicate created)
json
# 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 AppearsWhat It MeansWhat 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
json
{
  "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 exist

Common 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."
json
# 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:

json
# 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,3

Filtering by External ID:

json
# 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)

What's Next?

Continue your journey with these related concepts: