Device policy API returns PERMISSION_DENIED when updating security policy via setIamPolicy

We’re automating device security policy updates using the Google Cloud IoT Device Manager API and hitting a consistent PERMISSION_DENIED error when calling setIamPolicy. The same policy changes work perfectly through the console, but our automated deployment pipeline fails every time.

We’re trying to bind IAM roles at the device registry level to enable specific service accounts to manage device configurations. Here’s what we’re seeing:

request = service.projects().locations().registries().setIamPolicy(
    resource=registry_path,
    body={'policy': policy_object}
)
response = request.execute()  # PERMISSION_DENIED

The service account has roles/cloudiot.editor at the project level, which should be sufficient based on the documentation. We’ve verified the IAM bindings scope includes both the registry and device resources, but the API endpoint consistently rejects our requests while console operations succeed with the same credentials.

This is blocking our automated policy rollout to 200+ device registries across multiple regions. Has anyone encountered differences between API permissions versus console permissions for IoT policy management?

Thanks for the suggestions. I double-checked the resource path format and it’s correct. I also tried adding roles/cloudiot.deviceManager but still getting PERMISSION_DENIED. The policy object structure looks good - it’s the same structure we retrieve via getIamPolicy before modification. Could this be related to how the API validates permissions differently than the console? The service account definitely has the roles, I can see them in IAM console.

That makes sense! I’ll test with the security admin role added.

Check if you’re using the correct resource name format. The setIamPolicy endpoint is picky about the full resource path. Should be projects/{project}/locations/{location}/registries/{registry} without any trailing slashes or extra parameters. Also verify your policy object structure matches the expected format with bindings array and version field.

I’ve seen this before. The console uses your user credentials which likely have owner or editor at project level, while your service account might need explicit roles/cloudiot.deviceManager in addition to editor role. The setIamPolicy operation requires specific IAM admin permissions that aren’t included in the standard editor role.

The issue is likely the scope of your IAM binding. When you use setIamPolicy on a registry, you need roles/iam.securityAdmin or roles/resourcemanager.organizationAdmin to modify IAM policies themselves, not just cloudiot roles. The cloudiot.editor role lets you manage devices and configurations, but changing IAM policies requires IAM-specific permissions. Try adding roles/iam.securityAdmin at the project level for your service account. This is a common gotcha because the console often uses your personal credentials which have broader permissions.