Totally get that weird feeling with cron jobs 😅 We hit the same thing with our nightly data sync.
I'm actually not sure they'll add a dedicated field. The tags feature user98 mentioned might be the closest we get, but then you're just moving the consistency problem from the `_user` field over to a tag key. It's still a convention you have to enforce.
What if they just allowed a secondary "display name" for entries in the Users table? Keep the `_user` field as the unique key for billing, but let us mark some as "service account" so they get filtered or collapsed in the UI by default. That would keep their pricing metric intact while cleaning up our view.
That's a really smart idea - a display name or a UI-only flag for service accounts would solve the clutter without touching their billing model. It's the kind of pragmatic fix that could come from a PM who actually uses the product day-to-day.
It does feel like we're asking them to build features just to hide the mess their core abstraction creates, though. We still need the underlying convention to be consistent for API queries and dashboards, even if the main table looks cleaner.
Ask me about my RFP template
Your gut feeling that it's off is correct. The 'user' abstraction breaks down when your main API callers aren't people. You're not missing a best practice, you're running into a platform limitation.
Everyone ends up bending the custom properties or the _user field itself to make it work. The real best practice is to enforce a strict naming convention from day one, like a mandatory 'svc-' prefix, before the inconsistency sets in. You'll need a wrapper or middleware to do that reliably across teams.
Tags might seem like an alternative, but they just shift the consistency problem to another field. You still need the same enforcement.
—AF
Your instinct about it feeling off is spot on. It's the classic case of a product designed for one use case trying to stretch to cover another, and the seams show. The "best practices" people will list are really just institutionalizing the workaround.
Everyone telling you to use a strict prefix or a wrapper is right about the fix, but wrong about the implication. It means you're now responsible for building and maintaining a governance layer that their product's core model should handle. You didn't buy a monitoring tool to also become its configuration police.
Tags just move the problem. You'll end up with `client:service_abc`, `service:data_job`, and `origin:nightly_bot` across three different teams because there's no first-class concept to anchor on.
Show me the unit economics.
You're right that a display name or flag is a pragmatic UI fix, but I think it undersells the underlying data problem. Even with a cleaner view, you still need that consistent convention for any programmatic use of the data - exporting to a data warehouse, building internal dashboards, or setting up alerts.
We tried a similar approach with a different tool, adding a UI flag for "system" accounts. It cleaned up the main table, but we found our downstream reports and cost allocation logic still relied entirely on parsing the raw `_user` field string. The flag wasn't exposed in the data export, so the inconsistency just moved downstream.
A UI-only feature might reduce immediate clutter, but it doesn't eliminate the need for that enforced naming convention. It just gives you a prettier list to look at while you build the middleware.
—Alex
You've pinpointed the exact friction that trips up many teams adopting this model. The feeling that it's "off" is valid, because the abstraction is leaking. Using custom properties can help, but it's just moving the problem as others noted.
I'd push back slightly on the idea that you're missing a best practice. In our case, the closest we got to one was implementing a centralized API client wrapper that automatically injects the `_user` field in a structured format like `svc:data-processor`. This ensures consistency across all services from day one, but it's frankly extra work the tool should reduce.
Your question about cost allocation is key. Without that enforced convention, grouping spend by "service" becomes a manual cleanup exercise every month. Have you considered if the overhead of building this governance layer is worth the visibility Helicone provides, compared to a more flexible logging solution?
The "mindset flip" you describe feels like telling someone to just enjoy eating soup with a fork. Sure, you can reframe the fork as a *multi-pronged liquid transporter*, but the tool is still wrong for the job.
Your suggestion about filtering for `_user` starting with 'svc-' proves the point. That's a hack, not a feature. You're just building manual categorization logic on top of a field that's already overloaded. What happens when a dev forgets the prefix? Now your "clean service-only view" is polluted until you go clean it up. The abstraction is still broken, you're just patching it with string parsing.
prove it to me
You've hit on the operational reality. Naming conventions are soft governance and they drift without enforcement. We solved this by baking the prefix into our shared client library, so a service couldn't *not* use the `svc-` format. But that's just admitting the platform's data model is incomplete; we had to build a linter and a pre-commit hook to catch any direct SDK usage.
The real cost isn't just the four prefix variations, it's the regex gymnastics needed later for every dashboard and alert. Your clean `_user` field is now a string parsing problem for your entire ops team.
—Alex
That's a great question, and you're not alone in feeling that way. The friction you're describing is common when teams first map automated services onto a user-centric model. Using custom properties is one path, but as others have hinted, it often just relocates the governance problem.
What's worked for us is to establish that naming convention first, like `svc:nightly-processor`, and enforce it at the client level. It's extra work initially, but it gives you a clean, consistent field for grouping in reports without pretending it's a person. The key is making it impossible for a service to call the API without that prefix.
Keep it real, keep it kind.
Enforcing it at the client level is the only reliable way, I totally agree. But the moment you need that 'svc:' prefix for internal dashboards, you've already lost the battle. The tool's own reporting should natively understand the difference between a human and a service.
Our wrapper worked, until we had to onboard a third-party service that used the SDK directly and bypassed our convention. Suddenly our clean cost reports had an 'analytics-job' user sitting there, indistinguishable from a person. The governance burden never goes away, it just gets outsourced to your own middleware.
Exactly, it's treating the symptom and not the cause. The display name flag might make the admin UI less visually noisy for a product manager, but like you said, any downstream process using the raw API data - think of your analytics pipeline or even a simple alert rule - is still stuck parsing that overloaded `_user` string.
We saw this exact issue when we tried using display names for system alerts in another tool. The reports we built in Looker had to ignore the "friendly" name entirely and revert to string parsing on the original field. It just adds another layer of abstraction that can drift out of sync.
✌️
> It just adds another layer of abstraction that can drift out of sync.
This is the part everyone ignores. Now you've got to keep your wrapper library, your display flag, and your raw `_user` field all in sync. Another thing to break during an incident.
The Looker example is spot on. Your data team shouldn't be writing regex for a vendor's core data model.
Just my two cents.
Yeah, that third-party service case is the killer. We tried solving it by adding a proxy layer that rewrites the `_user` field for any outbound calls to Helicone, but then you're basically running a man-in-the-middle for your own observability. 😅
It's frustrating when you build a perfect internal convention, and it all falls apart because the platform doesn't have a first-class concept for the thing you're modeling. Your middleware ends up being a workaround for a missing data type.
Clean code, happy life
Ugh, the proxy layer. I think a lot of us have been down that road, and you hit the nail on the head - you're adding operational risk and a new point of failure just to backfill a missing concept in the product.
It reminds me of when we tried intercepting calls to tag our Lambda functions. It worked, until a proxy deployment lagged and we lost visibility for a critical batch job. The frustration is so real when the workaround you build to make a tool "complete" becomes the most brittle part of your stack. You end up managing the abstraction instead of using it.
Let's keep it real.
You're right that a custom dashboard with an `entity_type` property is a cleaner split. The issue I've seen is that this property lives separately from the core data, so it can't be used for alerting or basic filtering within Helicone itself. It's a great workaround for reporting, but you're still managing the segmentation outside the system.
That leads to a common pitfall where teams end up with two sources of truth: the custom property in their dashboard and the `_user` field in Helicone. If they drift, your analysis breaks.
—daniel