How can I assign a contact to the currently authenticated user with user-level OAuth?

We’re building a HubSpot app using user-level OAuth and are trying to assign newly created contacts to the HubSpot user who is currently using the app.

With the user-level OAuth token, we can identify the authenticated HubSpot user and obtain their user_id.

The problem is that when creating a contact, HubSpot requires the owner to be set using:

hubspot_owner_id

This appears to require the HubSpot Owner ID, which is different from the HubSpot user_id.

Normally, it looks like we could resolve this mapping using:

GET /crm/v3/owners/{userId}?idProperty=userId

However, the Owners API does not appear to be accessible using a user-level OAuth token.

This seems to leave a gap where we know which HubSpot user is making the request, but cannot determine the corresponding hubspot_owner_id required to assign the contact to them.

Has anyone found a supported way to handle this with user-level OAuth?

Specifically:

  1. Is there another API available to user-level OAuth tokens that returns the corresponding Owner ID?
  2. Is there a way to assign a new CRM record to the currently authenticated user without explicitly setting hubspot_owner_id?
  3. Is the expected approach to also maintain an account-level OAuth installation purely to resolve User ID → Owner ID mappings?
  4. Or is assigning ownership to the acting user currently unsupported for apps using only user-level OAuth?

Our use case is that each HubSpot user authorizes individually, and contacts created through the app should automatically be owned by the user performing the action.

Would appreciate any guidance on the intended pattern for this.

Hey @ajarbol,

Thanks for posting in the Community!

I’d like to tag in some Community experts here to see what insight they may have for us. @zach_threadint, @SteveHTM, and @stefen - are any of you able to provide some context for @ajarbol?

Shane, Community Manager

Hi @ajarbol :waving_hand:

In response to your questions:

  1. Is there another API available to user-level OAuth tokens that returns the corresponding Owner ID?
    • The Retrieve an owner endpoint is the one you’ll want to use. I’m guessing the reason it isn’t working for you at the moment is that your authentication tokens don’t currently have permission for this endpoint’s required scope: crm.objects.owners.read
  2. Is there a way to assign a new CRM record to the currently authenticated user without explicitly setting hubspot_owner_id?
    • Not as far as I know
  3. Is the expected approach to also maintain an account-level OAuth installation purely to resolve User ID → Owner ID mappings?
    • You could if you wanted to. But, unless I’m missing something, the critical part is making sure the given authentication token has access to the scope: crm.objects.owners.read. If this is the case, it shouldn’t matter if you decide to use Account-level or User-level OAuth token management.
  4. Or is assigning ownership to the acting user currently unsupported for apps using only user-level OAuth?
    • No. See above.

I hope it’s as simple as that. Please let me know if you have any follow-up questions.

Hey Zach

Thank you for taking the time and for your extremely quick response!

We followed that exact path.

The user-level token has crm.objects.owners.read (GET /oauth/v1/access-tokens/{token}). We then called Retrieve an owner as GET /crm/owners/2026-09/80960570?idProperty=userId for that same token’s user_id.

It returns 403:

{

“status”: “error”,

“message”: “User level OAuth token is not allowed for this endpoint.”

}

Not MISSING_SCOPES. Same 403 on /crm/owners/2026-03/{id} and /crm/v3/owners/{id}.

GET /crm/v3/owners and GET /crm/v3/owners?email=… both return 200 with this token, so owners read works on list/search — just not retrieve-by-id.

Can you confirm retrieve-by-id is blocked for user-level apps even with crm.objects.owners.read? If so, the docs should say that. It seems most owner ids are equal to user ids, so we’ll assume this for now and fall back to not assigning owners.

All the best!

Hey @ajarbol,

Thanks for providing the details of this error. Re-tagging @zach_threadint for visibility!

Shane, Community Manager

Hi @ajarbol :waving_hand:

Thanks for the additional information – that’s very helpful. I find it strange that it would work for the list owners endpoint, but not for the get owner by ID endpoint (regardless of the ID type used).

As far as I can tell, this isn’t currently covered in HubSpot Developer documentation. For example, in the Configure user-level access for an app documentation (specifically the API support section), there’s no mention of the Owners API – my interpretation of this omission is that it’s not supported, however your testing indicates that it seems to be at least partially supported (e.g. for the list owners endpoint).

I agree that this should be reviewed and clarified by HubSpot in all relevant developer documentation.

I hope this helps. Please let me know if you have any follow-up questions.

Confirming Zach’s answer from the other side, we ship an app that uses
user-level OAuth exactly this way, and the pattern works.

The only thing you need is crm.objects.owners.read in your scope list.
With that on a user-level token, you resolve the owner once at install
time and store it against the user:

  • read the user_id off the token metadata
  • look up the owner whose userId matches it
    (GET /crm/v3/owners/{userId}?idProperty=userId, or list owners and
    match if you want the whole roster anyway)
  • save that owner id alongside your user record, and stamp it as
    hubspot_owner_id on everything you create

No second account-level install needed, and there’s no implicit
“assign to the acting user” — you set hubspot_owner_id explicitly
every time, as Zach says.

Two things that cost us time:

  1. Adding the scope to an app that’s already installed isn’t enough on
    its own — existing installs have to re-authorize before the new scope
    takes effect, so during testing you can be staring at a 403 from a
    token that “has” the scope on paper.

  2. Not every authenticated user has an owner record. If someone connects
    without one, the lookup comes back empty and you get a record with no
    owner rather than an error. We treat that as a failed connect and tell
    the person why, instead of letting them through and writing unowned
    records.