# API key does or does not rely on permissions from user that created it

**URL:** https://discuss.elastic.co/t/api-key-does-or-does-not-rely-on-permissions-from-user-that-created-it/383663
**Category:** Elasticsearch
**Tags:** elastic-stack-security, ingest-pipeline
**Created:** [November 26, 2025, 12:18am UTC](https://discuss.elastic.co/t/api-key-does-or-does-not-rely-on-permissions-from-user-that-created-it/383663 "2025-11-26T00:18:44Z")
**Posts on this page:** 15
**Page:** 1

<div class="post-metadata">

### Author: ![qd-danh](https://avatars.discourse-cdn.com/v4/letter/q/43a26b/32.png) [@qd-danh](https://discuss.elastic.co/u/qd-danh)
#### Post date: [November 26, 2025, 12:18am UTC](https://discuss.elastic.co/t/api-key-does-or-does-not-rely-on-permissions-from-user-that-created-it/383663/1 "2025-11-26T00:18:44Z")

</div>

We had previously been creating API keys with our SSO user accounts. Then we found that after an SSO IdP provider change our users were effectively “different” such that we could no longer edit API keys (e.g. to add or remove permissions) and had to recreate a lot of them.

Because of that, we have created a shared user with very limited permissions, only to manage api keys, with the thought that even a future SSO change won't render our API keys orphaned (not editable). When I login with that user and create an API key - we get mixed results when then calling APIs with that key.

Index permissions seem to work ok. The code using the API key succeeds to post new documents to indices.

Cluster permissions are not working. Trying to call `/_ingest/pipeline/my-pipeline` and getting 403 response

```auto
action [cluster:admin/ingest/pipeline/get] is unauthorized for API key id [6w5rvZoByBvH2vsLbT-1] of user [fusion-apikeys-dev], this action is granted by the cluster privileges [read_pipeline,manage_ingest_pipelines,manage_pipeline,manage,all]"

```

API is created with this, which I understand with “role\_desriptors” does NOT leverage anything about the user that created the API key.

```auto
POST /_security/api_key
{
  "name": "ingest-pipeline-apikey-dev-333",
  "role_descriptors": {
    "ingest_pipeline_apikey_role_333": {
      "cluster": ["manage_pipeline", "read_pipeline", "manage_ingest_pipelines"]
    }
  }
}

```

Can you help sort me out?  
Are cluster permissions treated differently with API keys and the user that created them?

---

<div class="post-metadata">

### Author: ![stephenb](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/stephenb/32/40856_2.png) [@stephenb](https://discuss.elastic.co/u/stephenb)
#### Post date: [November 26, 2025, 3:09am UTC](https://discuss.elastic.co/t/api-key-does-or-does-not-rely-on-permissions-from-user-that-created-it/383663/2 "2025-11-26T03:09:07Z")

</div>

Hi @qd-danh

I am not sure I am following so lets define what privileges are applied to a created API Key

In short a user can not create an API Key with greater / more privileges than that user has at that point in time they create API Key.

The privileges that the API key results in in an intersection of the Current Users PIT privileges with the Roles defined in the API Key.

So the answer to your question in the title is ...Yes the API Key that is created does rely on the privileges of the user that creates it

As far as I know that applies to all privileges, cluster, indices, renote\_indicies, applications etc

This is documented here in the Roles section

> **[Create an API key
 | Elasticsearch API documentation](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-security-create-api-key)**
>
> All methods and paths for this operation:
> PUT
> /\_security/api\_key
> 
> 
> POST
> /\_security/api\_key
> Create an API ...

This approach keeps users from creating API Keys with more privileges than they currently have .. AKA does not allow privilege escalation which would be a security flaw.

---

<div class="post-metadata">

### Author: ![qd-danh](https://avatars.discourse-cdn.com/v4/letter/q/43a26b/32.png) [@qd-danh](https://discuss.elastic.co/u/qd-danh)
#### Post date: [November 26, 2025, 5:54pm UTC](https://discuss.elastic.co/t/api-key-does-or-does-not-rely-on-permissions-from-user-that-created-it/383663/3 "2025-11-26T17:54:46Z")

</div>

Thank you @stephenb for the confirmation.

Since the actual privileges for the API key are computed based on current user PIT privileges, it makes it tough to know those are. When I look at the API key either in Kibana or from GET API, I only see what roles are in the role\_descriptors from when the API was created.

Even if I look at the roles for the user that created it, I don't know what the roles were at the time when the API key was created.

Is there a way to see the actual computed privileges for the API key, or am I misunderstanding?

To explain our scenario a little more:  
We understand that only the creator of an API key can edit it later. If an SSO user creates an API key then is either OOO or no longer around, and we need to adjust privileges on the key, then we have to create a completely new key. So our thinking was to use a lower privileged non-SSO account that we share - to create API keys. But it's becoming apparent now that this "api creator" account needs to have the superset of privileges needed for the various API keys created. Make sense?

Thanks again.

---

<div class="post-metadata">

### Author: ![stephenb](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/stephenb/32/40856_2.png) [@stephenb](https://discuss.elastic.co/u/stephenb)
#### Post date: [November 26, 2025, 7:17pm UTC](https://discuss.elastic.co/t/api-key-does-or-does-not-rely-on-permissions-from-user-that-created-it/383663/4 "2025-11-26T19:17:19Z")

</div>

Lots to parse there ...

> [@qd-danh](#):
>
> Is there a way to see the actual computed privileges for the API key, or am I misunderstanding?

Yes to see what the PIT privileges from the user that created the key use the `with_limited_by` flag.

Use the GET API with

> `with_limited_by` boolean Generally available; Added in 8.5.0
> 
> Return the snapshot of the owner user's role descriptors associated with the API key. An API key's actual permission is the intersection of its assigned role descriptors and the owner user's role descriptors.

> **[Get API key information | Elasticsearch API documentation](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-security-get-api-key)**
>
> Retrieves information for one or more API keys.
> NOTE: If you have only the manage\_own\_api\_key privilege, this API returns only the API keys that you own.
> If you have read\_securi...

With respect to how you want to manage the user / how users create API keys there are several approaches... and it depends if you are using automation, your overal maturity etc... plus your overall security architecture / posture / philosopy. Who can / can not create API keys with what privileges is very use case dependent.

With respect to SSO users creating keys, yes that is an issue if they are disabled etc. that key can not be updated.

Personally I am not a big "Update API Keys" fan (there are valid uses cases), I prefer (preference) is to create new and rotate the keys which may or may not work for you, (understood that could be painful)

I have sophisticated customer where end users submit a PR / Github action, and their key shows up in a corp Secrets Store... a user never logs in directly to create an API key.

Generally, I see users use a "tightly managed" Native Realm user for API key creation.

But yes in basic the User that creates the key needs to have a Superset of the privileges that the key needs.

Hope that helps a bit....

---

<div class="post-metadata">

### Author: ![TimV](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/timv/32/13162_2.png) [@TimV](https://discuss.elastic.co/u/TimV)
#### Post date: [November 27, 2025, 3:11am UTC](https://discuss.elastic.co/t/api-key-does-or-does-not-rely-on-permissions-from-user-that-created-it/383663/5 "2025-11-27T03:11:08Z")

</div>

> [@qd-danh](#):
>
> We had previously been creating API keys with our SSO user accounts. Then we found that after an SSO IdP provider change our users were effectively “different” such that we could no longer edit API keys (e.g. to add or remove permissions) and had to recreate a lot of them.

Depending on how radical your changes were, you might be able to solve this with [Security Domains](https://www.elastic.co/docs/deploy-manage/users-roles/cluster-or-deployment-auth/security-domains).

But otherwise, yes, if you change your definition of user identity, then the user is no longer able to be identified as the owner of an API key (or other resources that depend on ownership).

---

<div class="post-metadata">

### Author: ![qd-danh](https://avatars.discourse-cdn.com/v4/letter/q/43a26b/32.png) [@qd-danh](https://discuss.elastic.co/u/qd-danh)
#### Post date: [December 12, 2025, 1:25am UTC](https://discuss.elastic.co/t/api-key-does-or-does-not-rely-on-permissions-from-user-that-created-it/383663/6 "2025-12-12T01:25:47Z")

</div>

Great thanks @stephenb for the tip about `with_limited_by` .  
Initially I was feeling comfortable with your answer and understood it. But now today I have a scenario that is not following that "intersection between API key and user that created it" and so now I'm confused. Thought I would illustrate it here, I am probably missing something else.

We use a Serilog sink in .NET that sends logging documents to Elastic, and at startup it makes a call to `GET /` to retrieve the Elastic server version so that it can adjust it's functionality.

Recently created an API key with my limited user and see an error at runtime, calling `GET /` needs `monitor` privilege. So using the logic you explained above, the API key AND the user that creates the key need `monitor` privilege. That's what I did (added role to the user and updated the API key). All is well. `GET /` now works.

But ... long ago, created an API key (mentioned above that the user who created it was through different SSO, so effectively an orphaned user) and that is using the same Serilog library. Presumably it's calling `GET /` at startup as well. Using the `with_limited_by` feature, the user that created it definitely has `monitor` priv (has cluster: all) **BUT** the API key does NOT have that permission. So ... how is that API key succeeding?

Showing the details here. THANK YOU if you see anything that would clear up my understanding.

This one works. Has cluster `monitor` priv in the API key AND the user that created it.

```json
{
  "api_keys": [
    {
      "id": "GZawLZoBmD8a3ZkIDdlN",
      "name": "(*REDACTED*)-logging-dev",
      "type": "rest",
      "creation": 1761703103827,
      "invalidated": false,
      "username": "(*REDACTED*)apikeys-dev",
      "realm": "native",
      "realm_type": "native",
      "metadata": {},
      "role_descriptors": {
        "document-writer": {
          "cluster": [
            "monitor"
          ],
          "indices": [
            {
              "names": [
                "(*REDACTED*)-logging-dev*"
              ],
              "privileges": [
                "create_index",
                "create",
                "create_doc",
                "write",
                "read",
                "index"
              ],
              "allow_restricted_indices": false
            }
          ],
          "applications": [],
          "run_as": [],
          "metadata": {},
          "transient_metadata": {
            "enabled": true
          }
        }
      },
      "limited_by": [
        {
          "manage-apikeys": {
            "cluster": [
              "manage_api_key",
              "manage_own_api_key",
              "monitor"
            ],
            "indices": [],
            "applications": [
              {
                "application": "kibana-.kibana",
                "privileges": [
                  "feature_dev_tools.all"
                ],
                "resources": [
                  "space:commandcenter",
                  "space:default"
                ]
              }
            ],
            "run_as": [],
            "metadata": {},
            "transient_metadata": {
              "enabled": true
            }
          }
        }
      ]
    }
  ]
}

```

This one **ALSO WORKS**. Only has the `monitor` privilege from the user that created it, not on the API key. But calling `GET /` with this API key succeeds.

```json
{
  "api_keys": [
    {
      "id": "i3Iz94MBZMpVBpglS5nf",
      "name": "(*REDACTED*)-logging-dev",
      "type": "rest",
      "creation": 1666299677667,
      "invalidated": false,
      "username": "3094815541",
      "realm": "cloud-saml-kibana",
      "metadata": {},
      "role_descriptors": {
        "document-writer": {
          "cluster": [],
          "indices": [
            {
              "names": [
                "(*REDACTED*)-logging-dev*"
              ],
              "privileges": [
                "create_index",
                "create",
                "create_doc",
                "write",
                "read"
              ],
              "allow_restricted_indices": false
            }
          ],
          "applications": [],
          "run_as": [],
          "metadata": {},
          "transient_metadata": {
            "enabled": true
          }
        }
      },
      "limited_by": [
        {
          "superuser": {
            "cluster": [
              "all"
            ],
            "indices": [
              {
                "names": [
                  "*"
                ],
                "privileges": [
                  "all"
                ],
                "allow_restricted_indices": false
              },
              {
                "names": [
                  "*"
                ],
                "privileges": [
                  "monitor",
                  "read",
                  "view_index_metadata",
                  "read_cross_cluster"
                ],
                "allow_restricted_indices": true
              }
            ],
            "applications": [
              {
                "application": "*",
                "privileges": [
                  "*"
                ],
                "resources": [
                  "*"
                ]
              }
            ],
            "run_as": [
              "*"
            ],
            "metadata": {
              "_reserved": true
            },
            "transient_metadata": {
              "enabled": true
            }
          }
        }
      ]
    }
  ]
}

```

---

<div class="post-metadata">

### Author: ![stephenb](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/stephenb/32/40856_2.png) [@stephenb](https://discuss.elastic.co/u/stephenb)
#### Post date: [December 12, 2025, 3:24pm UTC](https://discuss.elastic.co/t/api-key-does-or-does-not-rely-on-permissions-from-user-that-created-it/383663/7 "2025-12-12T15:24:34Z")

</div>

I will take a look when I can I notice the one that works unexpectedly is built on the `"realm": "cloud-saml-kibana"` realm. (not sure that has anything but just curious that the 2 "comparisions" are constructed different ways

Also can you also show the command you run + the output that is key to making debug easier. (I can guess but then .. just guessing)

---

<div class="post-metadata">

### Author: ![stephenb](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/stephenb/32/40856_2.png) [@stephenb](https://discuss.elastic.co/u/stephenb)
#### Post date: [December 12, 2025, 4:05pm UTC](https://discuss.elastic.co/t/api-key-does-or-does-not-rely-on-permissions-from-user-that-created-it/383663/8 "2025-12-12T16:05:23Z")

</div>

Hi @qd-danh

Hmmmm I get the expected results

```auto
POST /_security/api_key
{
  "name": "logs-reader-api-key",
  "role_descriptors": {
    "logs_reader_role": {
      "cluster": [],
      "indices": [
        {
          "names": ["logs-*"],
          "privileges": ["read", "view_index_metadata"]
        }
      ]
    }
  }
}

# Don't Worry not valid values :) 
 {
   "id": "mwFEdfgdfgKolCVLzxclb-",
   "name": "logs-reader-api-key",
   "api_key": "SXovvEdfgdfgdZT-IJWpLAlOA",
   "encoded": "bXdGRUU1cdhvdnZFZjg4YlpULdfhgdfghUlKV3BfgdffgdfgMQWxPQQ=="
}

```

Then Check Key

```auto
GET _security/api_key/?id=mwFEE5sBKolCVLzxclb-&with_limited_by=true

{
  "api_keys": [
    {
      "id": "mwFEE5sBKolCVLzxclb-",
      "name": "logs-reader-api-key",
      "type": "rest",
      "creation": 1765554811648,
      "invalidated": false,
      "username": "4212746406",
      "realm": "cloud-saml-kibana",
      "realm_type": "saml",
      "metadata": {},
      "role_descriptors": {
        "logs_reader_role": {
          "cluster": [],
          "indices": [
            {
              "names": [
                "logs-*"
              ],
              "privileges": [
                "read",
                "view_index_metadata"
              ],
              "allow_restricted_indices": false
            }
          ],
          "applications": [],
          "run_as": [],
          "metadata": {},
          "transient_metadata": {
            "enabled": true
          }
        }
      },
      "limited_by": [
        {
          "superuser": {
            "cluster": [
              "all"
            ],
            "indices": [
              {
                "names": [
                  "*"
                ],
                "privileges": [
                  "all"
                ],
                "allow_restricted_indices": false
              },
              {
                "names": [
                  "*"
                ],
                "privileges": [
                  "monitor",
                  "read",
                  "view_index_metadata",
                  "read_cross_cluster"
                ],
                "allow_restricted_indices": true
              }
            ],
            "applications": [
              {
                "application": "*",
                "privileges": [
                  "*"
                ],
                "resources": [
                  "*"
                ]
              }
            ],
            "run_as": [
              "*"
            ],
            "metadata": {
              "_reserved": true
            },
            "transient_metadata": {
              "enabled": true
            },
            "remote_indices": [
              {
                "names": [
                  "*"
                ],
                "privileges": [
                  "all"
                ],
                "allow_restricted_indices": false,
                "clusters": [
                  "*"
                ]
              },
              {
                "names": [
                  "*"
                ],
                "privileges": [
                  "monitor",
                  "read",
                  "view_index_metadata",
                  "read_cross_cluster"
                ],
                "allow_restricted_indices": true,
                "clusters": [
                  "*"
                ]
              }
            ],
            "remote_cluster": [
              {
                "privileges": [
                  "monitor_enrich",
                  "monitor_stats"
                ],
                "clusters": [
                  "*"
                ]
              }
            ]
          }
        }
      ]
    }
  ]
}

```

Looks Good

Then Try Key  
`GET /` Does Not Work as expected : `403`

```auto
curl -S -H "Authorization: ApiKey $API_KEY" -X GET "https://somerandomecluster.es.us-west1.gcp.cloud.es.io" | jq               

{
  "error": {
    "root_cause": [
      {
        "type": "security_exception",
        "reason": "action [cluster:monitor/main] is unauthorized for API key id [mwFEE5sBKolCVLzxclb-] of user [4212746406], this action is granted by the cluster privileges [monitor,manage,all]"
      }
    ],
    "type": "security_exception",
    "reason": "action [cluster:monitor/main] is unauthorized for API key id [mwFEE5sBKolCVLzxclb-] of user [4212746406], this action is granted by the cluster privileges [monitor,manage,all]"
  },
  "status": 403
}

```

Then try the search on the logs works

```auto
curl -S -H "Authorization: ApiKey $API_KEY" -X GET "https://somerandomecluster.es.us-west1.gcp.cloud.es.io/logs-*/_search?size=1" | jq

{
  "took": 83,
  "timed_out": false,
  "_shards": {
    "total": 176,
    "successful": 176,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 10000,
      "relation": "gte"
    },
    "max_score": 1.0,
    "hits": [
      {
        "_index": ".ds-logs-azure.eventhub-default-2025.11.30-000020",
        "_id": "90I275oBKolCVLzx4ypk",
        "_score": 1.0,
        "_source": {
          "cloud": {
            "availability_zone": "",
            "instance": {
              "name": "stephenb-logginig-test",
              "id": "2fce0248-0dbf-4e17-9750-911c9ebaa3fa"
            },
            "provider": "azure",
            "machine": {
              "type": "Standard_D2ads_v5"
            },
            "service": {
              "name": "Virtual Machines"
            },
            "region": "westus",
            "account": {
              "id": "ef3a5cc0-6972-4eac-94b7-e76a54c6aee8"
            }
          },
          "input": {
            "type": "azure-eventhub"
          },
          "agent": {
            "name": "stephenb-logginig-test",
            "id": "9eaf0e8f-a570-401b-8b63-0e4adb8e70ce",
            "ephemeral_id": "6d9427ff-267e-471a-b0de-e9f519d8d2ee",
            "type": "filebeat",
            "version": "8.19.5"
          },
          "@timestamp": "2025-12-05T15:52:13.081Z",
          "ecs": {
            "version": "8.11.0"
          },
    ....
}

```

So I am not seeing what your are seeing...

So perhaps you have something else going on. Somehow you're not using the API key you think you are, but I think I pretty much duplicated what you did and it seems to be honoring the privileges

---

<div class="post-metadata">

### Author: ![qd-danh](https://avatars.discourse-cdn.com/v4/letter/q/43a26b/32.png) [@qd-danh](https://discuss.elastic.co/u/qd-danh)
#### Post date: [December 24, 2025, 2:39am UTC](https://discuss.elastic.co/t/api-key-does-or-does-not-rely-on-permissions-from-user-that-created-it/383663/9 "2025-12-24T02:39:51Z")

</div>

After lots of spelunking in containers running in the cloud, I still don't know how the current API key is working. I have the api key stored in password manager, with its ID that I can cross reference with what we are using at runtime (even see the API key contents in key vault in lower environment). At runtime, the serilog elastic sink libraries are calling `GET /` which requires `monitor` and the container is NOT failing. But using that same API key in postman it fails as expected (required privs not present)

But I agree with you @stephenb. From my testing in postman with various keys created by various users I have with different permissions, I agree that the privileges for the API call need to be in BOTH the API KEY **and** the user privs at the time it was created.

The `with_limited_by=true` parameter in the `GET _security/api_key` call is very helpful.

Thank you for helping me troubleshoot this.

---

<div class="post-metadata">

### Author: ![stephenb](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/stephenb/32/40856_2.png) [@stephenb](https://discuss.elastic.co/u/stephenb)
#### Post date: [December 24, 2025, 3:32pm UTC](https://discuss.elastic.co/t/api-key-does-or-does-not-rely-on-permissions-from-user-that-created-it/383663/10 "2025-12-24T15:32:22Z")

</div>

@qd-danh Your Welcome

> [@qd-danh](#):
>
> At runtime, the serilog elastic sink libraries are calling `GET /`

I am looking at the Serilog Elasticsearch Sink...

> **[Elastic.Serilog.Sinks 9.0.0](https://www.nuget.org/packages/Elastic.Serilog.Sinks)**
>
> Package Description

---

<div class="post-metadata">

### Author: ![stephenb](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/stephenb/32/40856_2.png) [@stephenb](https://discuss.elastic.co/u/stephenb)
#### Post date: [December 24, 2025, 6:10pm UTC](https://discuss.elastic.co/t/api-key-does-or-does-not-rely-on-permissions-from-user-that-created-it/383663/11 "2025-12-24T18:10:32Z")

</div>

so @qd-danh

So I admit I am a bit confused this whole thread was based on "reader" privileges but `serilog` needs writer / publisher provileges and indeed requires `monitor` priviliges

The proper privileges for publishing to a data stream should be

```auto
POST /_security/api_key
{
  "name": "logs_writer",
  "role_descriptors": {
    "logs_writer_role": {
      "cluster": ["monitor", "read_ilm", "read_pipeline"],
      "index": [
        {
          "names": ["logs-*"],
          "privileges": ["view_index_metadata", "create_doc", "auto_configure"]
        }
      ]
    }
  }
}

```

I ran this with a small CS program using the Elasticsearch Serilog and it worked as expected.

---

<div class="post-metadata">

### Author: ![qd-danh](https://avatars.discourse-cdn.com/v4/letter/q/43a26b/32.png) [@qd-danh](https://discuss.elastic.co/u/qd-danh)
#### Post date: [December 30, 2025, 12:27am UTC](https://discuss.elastic.co/t/api-key-does-or-does-not-rely-on-permissions-from-user-that-created-it/383663/12 "2025-12-30T00:27:38Z")

</div>

OK I think I finally have this narrowed down and figured out. I agree with what you have said above, `monitor` is required to call `GET /` … BUT … it looks like Serilog gracefully swallows that issue, depending on the type of 403 error.

_Sorry for the length - wanted to give as much detail so others in the future might find this answer more quickly._

There are a couple things going on, originally conflated them into the same thing, which caused my confusion. @stephenb I can see above where we got a little switched over to reader privileges but the original question / issue is about the need for `monitor` privilege (in addition to `create` and `auto_configure` for the actual log messages)

**403 - Issue #1. Non standard port**  
This is my corporate network and VPN not allowing outbound calls to port 9423.  
The initial call by Serilog to `GET /` was returning 403, and initially I was not noticing that it was due to a socket error, not a permissions issue. In this case, not only the `GET /` fails at startup, but every attempt to send log messages as well.  
_Fixed: use regular port 443._

 ![01-bad-port](https://us1.discourse-cdn.com/elastic/original/3X/2/f/2faf16ba862f56873b8c6155f09b5277e399b8f4.png)

**403 - Issue #2. No privilege**  
This response says that the provided API key does not have permission to call `GET /` (needs 'monitor') BUT … Serilog seems to quietly swallow this error and try to continue processing. @stephenb you point this out in your reply above. (source code link below)

Notice the `GET /` fails but then the actual log messages succeed.

 ![02-no-monitor-priv](https://us1.discourse-cdn.com/elastic/original/3X/0/1/0120f98468336c1e9d3f30fd991c2420eebdf31d.png)

 ![03-logs-succeed-after-403](https://us1.discourse-cdn.com/elastic/original/3X/7/4/74f02918882fe278069d375d595517282bdb23d6.png)

**SelfLog**  
Since Serilog is quietly swallowing the error with `GET /` and proceeding to successfully upload log messages, the only way we know that `GET /` is failing is when SelfLog is turned on. We were doing this on a new project to make sure keys are working. Seeing 403 in that initial error and heading down this rabbit hole.

**Summary / TL;DR**  
Probably best if 'monitor' privilege is granted, so Serilog can do what it needs to (fallback functionality based on version, etc) but it's not required to successfully send log messages. This is why our existing 2-year-old code is working fine: SelfLog is disabled by default, so we don't hear about the 403 at startup, and then we get all the log messages we expect.

Grant the user creating the API key AND the API key itself the cluster `monitor` privilege as stated, and all succeeds as expected.

**Example API create**

```auto
POST /_security/api_key
{
  "name": "logging-test-privs",
  "role_descriptors": {
    "document-writer": {
      "cluster": ["monitor"],
      "indices": [
        {
          "names": [
            "cmdcenter-logging-dev*"
          ],
          "privileges": [
            "create",
            "auto_configure"
          ]
        }
      ]
    }
  }
}

```

**Serilog Source Code / Fallback Logic**  
This is the code for the Serilog Elasticsearch sink nuget package.  
From what I can tell, this specific fuction is where the call to GET / happens.

> <https://github.com/serilog-contrib/serilog-sinks-elasticsearch/blob/1e9777c3034c2d8d078f60822c77b9caad5b7870/src/Serilog.Sinks.Elasticsearch/Sinks/ElasticSearch/ElasticsearchVersionManager.cs#L55>

Shows right there in this same code file that they fallback to default version 7.17 if the GET fails, and explains why despite the fail at startup, **the rest of the log uploads work even without the `monitor` privilege on the API key and/or the user that created it**.

Thanks again for all the help.

---

<div class="post-metadata">

### Author: ![stephenb](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/stephenb/32/40856_2.png) [@stephenb](https://discuss.elastic.co/u/stephenb)
#### Post date: [December 30, 2025, 1:41am UTC](https://discuss.elastic.co/t/api-key-does-or-does-not-rely-on-permissions-from-user-that-created-it/383663/13 "2025-12-30T01:41:48Z")

</div>

@qd-danh

Good finds and thank you so much for following up for other members of the community.

As a final followup

With Elastic Cloud you can always use port `443` for any connection port `9243` is a legacy port which is still valid if you want to use it for now, but `443` is the future.

In addition, there are some components that default to the default ports such as 9200, 5601, 8220 and so I always test / validate with 443.

In the past / future, if you had mentioned you were using elastic cloud, I might have told you perhaps check 9243. In the past we ran into many customers with corporate firewalls that did not allow 9243

---

<div class="post-metadata">

### Author: ![stephenb](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/stephenb/32/40856_2.png) [@stephenb](https://discuss.elastic.co/u/stephenb)
#### Post date: [December 31, 2025, 7:38pm UTC](https://discuss.elastic.co/t/api-key-does-or-does-not-rely-on-permissions-from-user-that-created-it/383663/14 "2025-12-31T19:38:44Z")

</div>

@qd-danh BTW, the Elasticsearch Serilog Sink you are referring to is archived. You should really be using the Maintained Version , maintained by Elasticsearch

> **[Elastic.Serilog.Sinks 9.0.0](https://www.nuget.org/packages/Elastic.Serilog.Sinks)**
>
> Package Description

> **[GitHub - elastic/ecs-dotnet](https://github.com/elastic/ecs-dotnet)**
>
> Contribute to elastic/ecs-dotnet development by creating an account on GitHub.

---

<div class="post-metadata">

### Author: ![qd-danh](https://avatars.discourse-cdn.com/v4/letter/q/43a26b/32.png) [@qd-danh](https://discuss.elastic.co/u/qd-danh)
#### Post date: [December 31, 2025, 10:08pm UTC](https://discuss.elastic.co/t/api-key-does-or-does-not-rely-on-permissions-from-user-that-created-it/383663/15 "2025-12-31T22:08:43Z")

</div>

Ooh - thank you for the heads up. Work item created to update to **Elastic.Serilog.Sinks**.
