# User API keys specification

**URL:** https://meta.discourse.org/t/user-api-keys-specification/48536
**Category:** Integrations
**Tags:** rest-api, reference
**Created:** [August 12, 2016, 2:11am UTC](https://meta.discourse.org/t/user-api-keys-specification/48536 "2016-08-12T02:11:53Z")
**Posts on this page:** 20
**Page:** 1

<div class="post-metadata">

### Author: ![Discourse](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/discourse/32/148734_2.png) [@Discourse](https://meta.discourse.org/u/Discourse)
#### Post date: [August 12, 2016, 2:11am UTC](https://meta.discourse.org/t/user-api-keys-specification/48536/1 "2016-08-12T02:11:53Z")

</div>

Discourse contains a system for generating API keys **per user** if a very specific protocol is followed. This feature facilitates “application” access to Discourse instances without needing to involve moderators.

### High level description

At a high level:

1. Client (desktop app, browser plugin, mobile app) generates a private/public key pair and return url

2. Client redirects to a route on discourse giving discourse its public key

3. Discourse gets approval from user to use app

4. Discourse generates a user api key

5. Discourse redirects back to return url with an encrypted payload using public api key containing the user api key

### Details

Use cases:

1. Desktop applications that poll Discourse sites on behalf of end users to get notification counts across multiple sites.

2. Mobile applications that poll Discourse sites on behalf of end users and handles push notifications

3. Web applications that provide a dashboard for end users about various Discourse sites.

4. Custom integrations with 3rd party apps that consume Discourse as part of a general company app. Eg: integrate Discourse community notifications into hopscotch app.

The design:

### Site Settings

- **allow\_user\_api\_key\_scopes** : allowed access scopes for user api keys. Scopes are defined [here](https://github.com/discourse/discourse/blob/main/app/models/user_api_key_scope.rb). The available built-in scopes are: `read`, `write`, `message_bus`, `push`, `one_time_password`, `notifications`, `session_info`, `bookmarks_calendar`, `user_status` (plugins may register additional scopes).

- **user\_api\_key\_allowed\_groups** : controls which groups are allowed to generate user API keys (defaults to admins, moderators, and trust\_level\_0)

- **allowed\_user\_api\_push\_urls** : list of sites that can be targets for push notifications

- **allowed\_user\_api\_auth\_redirects** : allowed redirect destinations after user api key generation

### Global Settings

- **max\_user\_api\_reqs\_per\_minute** : 50
- **max\_user\_api\_reqs\_per\_day** : 4000

### UX elements

If any user api keys have been granted, Discourse displays an **apps** tab in the user page.

The **apps** tab will list:

- The name of the application eg: (“Discourse Notifier”)
- Last use date
- Approved date
- List of access scopes granted
- A **revoke access** button so you can easily revoke any keys

#### API Key authorization UI

Every key will have to be explicitly authorized by end users in a page that clearly explains what is going on, for example:

> “Discourse Notifier” is requesting the following access to your account:
> 
> - Read and clear notifications
> - Read user session info
> - Create a one-time login token
> 
> [Authorize]

### API key generation flow

API only requires a single GET request on the user’s end.

```plaintext
https://sitename.com/user-api-key/new

```

> ❗ As of Discourse 2.1 these user api keys now auto-expire if left unused for long periods of time. The site setting: `revoke user api keys unused days` is set to 180 out of the box

Params:

- **auth\_redirect** : url to redirect back to with the generated token
- **application\_name** : the name of the application making the request (will be displayed in the user account’s Apps tab)
- **client\_id** : a unique identifier for the client
- **nonce** : a unique nonce generated by the client. This will be echoed back in the encrypted payload so the client can verify the response authenticity
- **scopes** : comma-separated list of access scopes allowed for the key, see `allow user api key scopes` for the full list of available scopes
- **push\_url** : url to push notifications to (required and valid only if `push` or `notifications` are included in the scopes)
- **public\_key** : the public part of the keypair generated by the client
- **padding** (optional): the RSA padding mode to use for encrypting the payload. Accepted values are `pkcs1` (default) or `oaep`. OAEP is recommended for new applications

After `/user-api-key/new` is called with correct params 2 things may happen

1. If user is not logged on, we will redirect to login (after login we will resume authorization)
2. Once a user is logged on they will be presented with the authorization UI

After authorization is allowed, system will redirect back to the URL defined in `auth_redirect` and include an encrypted `payload` parameter containing a JSON object with the generated user API key (`key`), the `nonce`, push status (`push`), and API version (`api`). If the `one_time_password` scope was requested, a separate encrypted `oneTimePassword` query parameter will also be included. `client_id` is not echoed back for extra security.

### Checking API version

The use key API in Discourse is versioned. Clients can check a Discourse site’s API version by making a HEAD request to `https://sitename.com/user-api-key/new`. The response will contain a header named Auth-Api-Version containing the version number of the site’s API.

### Consuming the API

Consuming the client API will be somewhat different that the current admin API.

Client can specify 2 headers:

`User-Api-Key` (required): the key that was generated

and

`User-Api-Client-Id` (optional): supply this to update the ‘client id’ stored for this api\_key in the database.

Once those headers are specified client can perform requests against the API just as they would normally.

### Generating a one-time-login password

As of version 4, the API includes a special scope: the `one_time_password` scope, which allows clients to use the user API key to generate a one-time-password. If the client includes this scope when generating the API key following the steps above, an encrypted `oneTimePassword` will be included as a separate query parameter in the redirect back to the client.

Alternately, the client can make a GET request to `/user-api-key/otp` with the following parameters:

- auth\_redirect
- application\_name
- public\_key
- padding (optional)

and with the `User-Api-Key` header.

This request will redirect to a screen in Discourse that will ask the user to allow the application access to the site. If the user approves, the site will redirect back to the URL defined in `auth_redirect` and include an encrypted `oneTimePassword` parameter with a one-time password which the client can use to login to the site by requesting `https://sitename.com/session/otp/ONE-TIME-PASSWORD`. (The one-time-password is only valid for 10 minutes.)

### Revoking API keys

To revoke an API key, make a POST request with the `User-Api-Key` header and no params to `/user-api-key/revoke`.

* * *

_Last Reviewed by @SaraDev on 2022-07-13T00:00:00Z_

---

<div class="post-metadata">

### Author: ![elijah](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/elijah/32/104055_2.png) [@elijah](https://meta.discourse.org/u/elijah)
#### Post date: [August 12, 2016, 5:20am UTC](https://meta.discourse.org/t/user-api-keys-specification/48536/2 "2016-08-12T05:20:35Z")

</div>

There are places I’ve strongly considered writing a delayed post tool, so I can compose a bunch of “word of the day” type things to keep going while I’m on vacation. Doing it without proper API access and pretending to be a browser seems somewhat more complicated than on other forum software.

(Just as an example of another use case.)

---

<div class="post-metadata">

### Author: ![sam](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/sam/32/102149_2.png) [@sam](https://meta.discourse.org/u/sam)
#### Post date: [August 12, 2016, 5:30am UTC](https://meta.discourse.org/t/user-api-keys-specification/48536/3 "2016-08-12T05:30:56Z")

</div>

That is a good example, but keep in mind, our default will be only to enable read tokens, not write ones. Site admins will have to opt to allow write tokens.

Completely agree that the `_t` cookie hacks are a huge problem.

---

<div class="post-metadata">

### Author: ![jesselperry](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/jesselperry/32/119501_2.png) [@jesselperry](https://meta.discourse.org/u/jesselperry)
#### Post date: [September 11, 2016, 5:47pm UTC](https://meta.discourse.org/t/user-api-keys-specification/48536/4 "2016-09-11T17:47:26Z")

</div>

Does **max\_user\_api\_calls\_per\_key\_day** setting apply to admin-created keys at `/admin/api`?

---

<div class="post-metadata">

### Author: ![sam](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/sam/32/102149_2.png) [@sam](https://meta.discourse.org/u/sam)
#### Post date: [September 11, 2016, 9:35pm UTC](https://meta.discourse.org/t/user-api-keys-specification/48536/5 "2016-09-11T21:35:24Z")

</div>

Nope only to user api keys, we can look at adding extra limits for standard Api keys, it is a good safeguard

---

<div class="post-metadata">

### Author: ![sckott](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/sckott/32/115359_2.png) [@sckott](https://meta.discourse.org/u/sckott)
#### Post date: [October 4, 2018, 8:51pm UTC](https://meta.discourse.org/t/user-api-keys-specification/48536/9 "2018-10-04T20:51:07Z")

</div>

To be clear, this is not implemented yet, correct?

---

<div class="post-metadata">

### Author: ![RGJ](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/rgj/32/523185_2.png) [@RGJ](https://meta.discourse.org/u/RGJ)
#### Post date: [October 4, 2018, 9:35pm UTC](https://meta.discourse.org/t/user-api-keys-specification/48536/10 "2018-10-04T21:35:59Z")

</div>

No, it’s there (for almost 2,5 years now) - Admin - Settings - User API.

---

<div class="post-metadata">

### Author: ![codinghorror](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/codinghorror/32/110067_2.png) [@codinghorror](https://meta.discourse.org/u/codinghorror)
#### Post date: [October 5, 2018, 3:26am UTC](https://meta.discourse.org/t/user-api-keys-specification/48536/11 "2018-10-05T03:26:32Z")

</div>

As of Discourse 2.1 these user api keys now auto-expire if left unused for long periods of time, correct @sam?

---

<div class="post-metadata">

### Author: ![sam](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/sam/32/102149_2.png) [@sam](https://meta.discourse.org/u/sam)
#### Post date: [October 5, 2018, 5:48am UTC](https://meta.discourse.org/t/user-api-keys-specification/48536/12 "2018-10-05T05:48:26Z")

</div>

Correct the site setting: `expire user api keys days` is set to 180 out of the box.

---

<div class="post-metadata">

### Author: ![sckott](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/sckott/32/115359_2.png) [@sckott](https://meta.discourse.org/u/sckott)
#### Post date: [October 17, 2018, 2:26pm UTC](https://meta.discourse.org/t/user-api-keys-specification/48536/13 "2018-10-17T14:26:45Z")

</div>

> [@RGJ](#):
>
> No, it’s there (for almost 2,5 years now) - Admin - Settings - User API.

Okay, but you have to have admin access to see that page yes? And as an admin, it seems I can only create 1 key, so can’t create many keys for different users. confused.

---

<div class="post-metadata">

### Author: ![blake](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/blake/32/157322_2.png) [@blake](https://meta.discourse.org/u/blake)
#### Post date: [October 17, 2018, 6:14pm UTC](https://meta.discourse.org/t/user-api-keys-specification/48536/14 "2018-10-17T18:14:39Z")

</div>

What are you trying to accomplish? It’s not clear because there is this (user api keys) and regular API tokens (which is what I think you are after) that admins can create for multiple users, but you have to do that from the individual user’s page inside of the admin dashboard by clicking on the “generate” button on the “API Key” field.

 ![image](https://global.discourse-cdn.com/meta/original/3X/2/8/28a7e2297c0ba388a7caa4f47f9b743880c4c661.png)

---

<div class="post-metadata">

### Author: ![sckott](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/sckott/32/115359_2.png) [@sckott](https://meta.discourse.org/u/sckott)
#### Post date: [October 18, 2018, 5:50pm UTC](https://meta.discourse.org/t/user-api-keys-specification/48536/15 "2018-10-18T17:50:32Z")

</div>

Thanks! Yes, “regular API tokens” for users is what i was after. I’m sure I’d seen that a million times, and never realized it was there. ✔

---

<div class="post-metadata">

### Author: ![vsoch](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/vsoch/32/124967_2.png) [@vsoch](https://meta.discourse.org/u/vsoch)
#### Post date: [December 6, 2018, 3:29pm UTC](https://meta.discourse.org/t/user-api-keys-specification/48536/16 "2018-12-06T15:29:57Z")

</div>

Hey everyone I’m not sure if this was resolved? I see that it’s possible to create an “All Users” and (per user) level API token _as an admin_, but I’m interested in giving a user the power to generate his/her own token. Has there been work done on this? It sounds like the original thread was getting at this idea, and then it was lost. Thanks!

---

<div class="post-metadata">

### Author: ![sam](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/sam/32/102149_2.png) [@sam](https://meta.discourse.org/u/sam)
#### Post date: [December 6, 2018, 9:22pm UTC](https://meta.discourse.org/t/user-api-keys-specification/48536/17 "2018-12-06T21:22:58Z")

</div>

Download the mobile App and then add a site, that uses the user api key system you have to follow a very strict workflow, no plans to expose arbitrary generation of keys in user prefs

---

<div class="post-metadata">

### Author: ![vsoch](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/vsoch/32/124967_2.png) [@vsoch](https://meta.discourse.org/u/vsoch)
#### Post date: [December 6, 2018, 9:47pm UTC](https://meta.discourse.org/t/user-api-keys-specification/48536/18 "2018-12-06T21:47:02Z")

</div>

Thank you! I downloaded the app, and then realized that the sites I belong to don’t have this enabled (I’ve been testing on my local machine with the Dockerized discourse). Just to make sure we are talking about the same thing - I’m interested in generating tokens to use just a scoped subset of functions (and not global API keys to _do all the things_). Is this what you are talking about?

It seems like, given that there is an [endpoint to generate tokens](https://docs.discourse.org/#tag/Admin%2Fpaths%2F~1admin~1users~1%7Bid%7D~1generate_api_key%2Fpost), it would be logical to provide this function (to users with a certain trust level, for example) from within the site. Otherwise, it would need to be the case that the site generates some external page (with a server to hide an admin token) to generate the keys for the user. Is it the case that 1. there is no internal generation of tokens for the user (and why not?) and 2. Nobody has created some external app (javascript, flask, anything really) to perform this action?

---

<div class="post-metadata">

### Author: ![sam](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/sam/32/102149_2.png) [@sam](https://meta.discourse.org/u/sam)
#### Post date: [December 6, 2018, 11:05pm UTC](https://meta.discourse.org/t/user-api-keys-specification/48536/19 "2018-12-06T23:05:19Z")

</div>

> [@vsoch](#):
>
> Is this what you are talking about?

Yes.

> [@vsoch](#):
>
> it would be logical to provide this function (to users with a certain trust level, for example) from within the site. Otherwise, it would need to be the case that the site generates some external page (with a server to hide an admin token) to generate the keys for the user. Is it the case that 1. there is no internal generation of tokens for the user (and why not?) and 2. Nobody has created some external app (javascript, flask, anything really) to perform this action?

Possibly, conceptually

1. This is tricky to consume cause you need to use custom HTTP headers (by design)

2. It would be very hard to educate even TL3 users about what the point is of this thing.

I prefer not to ship UI for this in core, but maybe you should write a plugin for your specific use case?

Can you explain a bit more about the “why” here?

The design of the system centers around “I have an external dedicated program”, “this program knows how to follow Discourse protocol”, “It asks for token”

API keys (non user ones) are much easier to consume cause you just append them after the `?`

---

<div class="post-metadata">

### Author: ![ymi](https://avatars.discourse-cdn.com/v4/letter/y/dec6dc/32.png) [@ymi](https://meta.discourse.org/u/ymi)
#### Post date: [January 24, 2019, 9:19am UTC](https://meta.discourse.org/t/user-api-keys-specification/48536/22 "2019-01-24T09:19:41Z")

</div>

> [@sam](#):
>
> ### Consuming the API
> 
> Consuming the client API will be somewhat different that the current admin API.
> 
> Client can specify 2 headers:
> 
> `User-Api-Key` (required): the key that was generated
> 
> and
> 
> `User-Api-Client-Id` (optional): supply this to update the ‘client id’ stored for this api\_key in the database.
> 
> Once those headers are specified client can perform requests against the API just as they would normally.

Can I pass the API key generated from the user page on this? I was trying to use that API key and seeing ‘You are not permitted to view the requested resource.’ error on /categories.json API request

---

<div class="post-metadata">

### Author: ![Alan\_Murphy](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/alan_murphy/32/139391_2.png) [@Alan\_Murphy](https://meta.discourse.org/u/Alan_Murphy)
#### Post date: [August 8, 2019, 4:40pm UTC](https://meta.discourse.org/t/user-api-keys-specification/48536/23 "2019-08-08T16:40:08Z")

</div>

Hi guys

Apologies if this is duplication but I am having trouble digesting the steps there even though they are very well put together

I have a discourse installation on `https://forum.domain.com` and a site on `https://development.domain.com` from which I need to make calls to the discourse installation to pull and set some data.

All requests work from postman with Api-Username and Api-Key - no problem

However for cross origin requests using JS discourse doesnt allow the Api-Username leading me down the path of the flow described in this thread i.e. using the User-Api-Key and the User-Api-Client-Id

I basically require a description of paramaters PUBLIC\_KEY,NONCE,CLIENTID used in the sample request below and where I can get them…

`https://forum.domain.com/user-api-key/new?public_key=PUBLIC_KEY&nonce=NONCE&scopes=SCOPES&client_id=CLIENTID&application_name=DEVELOP&auth_redirect=XXX`

Finally will this flow allow for seamless api requests from my `https://development.domain.com` to `https://forum.domain.com` without the need for authentication.

PS I have SSO set up between the forum and the site from which the requests are being made so the user will be logged in..

Thanks for any guidance

---

<div class="post-metadata">

### Author: ![sam](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/sam/32/102149_2.png) [@sam](https://meta.discourse.org/u/sam)
#### Post date: [August 9, 2019, 3:27am UTC](https://meta.discourse.org/t/user-api-keys-specification/48536/24 "2019-08-09T03:27:33Z")

</div>

One sec, is this a per user thing? Are you allowing arbitrary users to do this?

Our server API supports header based auth these days so it can work with CORS just like the user api keys do. You would use the user api keys if you want to restrict scopes and allow end users to generate the keys vs admins.

---

<div class="post-metadata">

### Author: ![Alan\_Murphy](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/alan_murphy/32/139391_2.png) [@Alan\_Murphy](https://meta.discourse.org/u/Alan_Murphy)
#### Post date: [August 9, 2019, 9:09am UTC](https://meta.discourse.org/t/user-api-keys-specification/48536/25 "2019-08-09T09:09:47Z")

</div>

I wouldn’t see each user requiring a separate key no… One admin key should do what I need.

I can consume the api no problem through postman. For example a GET to /notifications.json?username=alanmurphy returns the data no problem using just api-key as a header.

If I trigger that request from the console of a discourse installation I get data back no problem also i.e.

var xhr = new XMLHttpRequest();  
xhr.addEventListener(“readystatechange”, function () {  
if (this.readyState === 4) {  
console.log(this.responseText);  
}  
});  
xhr.open(“GET”, “https://\*\*\*\*\*\*\*\*\*\*.com/notifications.json?username=alanmurphy”);  
xhr.setRequestHeader(“api-key”, “d06ca53322d1fbaf383a6394d6c229e56871342d2cad953a0fe26c19df7645ba”);  
xhr.setRequestHeader(“api-userame”, “system”);  
xhr.send();

All Good:+1:

However, if I do this from the sub-domain I am wishing to contact discourse from, it tells me that it has been blocked by CORS policy: Request header field api-key is not allowed by Access-Control-Allow-Headers.

The allowed headers are:

Content-Type, Cache-Control, X-Requested-With, X-CSRF-Token, Discourse-Visible, User-Api-Key, User-Api-Client-Id

If I could just get guidance on what auth params to pass for cross origin requests and where to get them that would be very helpful.

Ps. My discourse is set up to allow Cross Origin requests from the domain so I believe it is purely a headers issue

Thanks

[Next page](https://meta.discourse.org/t/user-api-keys-specification/48536.md?page=2)
