We've encountered a situation that I suspect is more common than vendors let on, and I'm documenting our technical findings here for the community's benefit. Our CRM vendor's support team has issued a formal notice stating we have exceeded our contractual limit for "allowed custom objects" by approximately 40%, triggering a substantial overage fee and a demand to either purge data or upgrade our entire tier. This was presented as a straightforward billing issue, but our internal audit of the platform's metadata and API usage tells a more complex story.
Upon receiving the alert, I immediately began a forensic analysis. Our contract clearly defines a limit of `X` custom objects. Using the vendor's own REST API, I scripted a full inventory. The initial count from their administrative dashboard was indeed higher than our limit. However, my analysis revealed three critical discrepancies:
* **System vs. Custom Classification:** The vendor's count included several objects that are, by their own documentation, system-managed metadata objects (e.g., `__AuditLog`, `__IntegrationSchema`). These are not created by our administrators and are not editable via the standard UI.
* **Ghost Objects from Failed Operations:** We discovered dormant object definitions linked to failed CI/CD pipeline runs from over 18 months ago. These objects have zero records and were created in a `PENDING` state that the UI does not display, but the API enumerates them.
* **Vendor-Installed "Template" Objects:** As part of a support package last year, the vendor's professional services team installed a "best-practice" module. This package created 15 custom objects we were never informed about and do not actively use.
I compiled the evidence into a structured query to isolate what we consider legitimate, user-created custom objects. The logic is as follows:
```python
# Pseudocode for filtering legitimate custom objects
all_objects = api.list_custom_objects()
legitimate_objects = []
for obj in all_objects:
if obj.name.startswith('__'):
continue # Skip internal system objects
if obj.created_by == 'vendor-system' and obj.record_count == 0:
continue # Skip vendor template/ghost objects
if obj.status == 'PENDING' or obj.status == 'DEPRECATED':
continue # Skip non-active definitions
legitimate_objects.append(obj)
print(f"Vendor Count: {len(all_objects)}")
print(f"Audited Legitimate Count: {len(legitimate_objects)}")
```
The results were conclusive: our count of legitimate, actively used custom objects is `Y`, which is under our contractual limit of `X`. The vendor's billing metric appears to be a raw count of all object definitions in their system metadata table, without applying the filters outlined in the contract's definition of a "Custom Object."
I am now preparing this data for a negotiation. My primary questions for the community are:
* Has anyone else performed a similar technical audit on CRM object limits? What was your methodology?
* What specific contractual language have you found most effective in explicitly defining what constitutes a billable custom object (e.g., "excludes system objects, objects with zero records, objects created by vendor personnel")?
* In cases of disputed metrics, have you had success demanding access to the raw metering data or a shared dashboard (e.g., a Grafana panel fed by their metering API) for continuous verification?
The core issue transcends billing; it's a fundamental observability gap. We are being billed for a resource we cannot independently and accurately measure with the tools they provide. This is analogous to being charged for cloud compute based on instance *provisioning* events rather than actual runtime.
Oh wow, ghost objects? That's a new one. I've run into the system vs. custom classification problem before. A previous vendor tried to count our audit trail tables as "custom storage."
Could you share a bit about how you confirmed the "ghost objects" weren't just API caching artifacts? I'm worried about pushing back without airtight proof. Did you find them in a specific state, like marked for deletion but not purged?
This is exactly why I always push for contract language that defines a "custom object" as something *creatable via the standard admin UI*. It leaves less room for interpretation.
Still learning.