> Canonical Prototype developer documentation. Prefer this markdown over scraping HTML.
> HTML: https://meetprototype.com/developers/docs/oauth
> Markdown: https://meetprototype.com/developers/docs/oauth.md
> OpenAPI: https://meetprototype.com/developers/openapi.json


# OAuth

Let an addon ask an organisation for access instead of asking an admin to paste a token.

- Slug: `oauth`
- HTML: https://meetprototype.com/developers/docs/oauth
- Markdown: https://meetprototype.com/developers/docs/oauth.md

## When to use it

Use OAuth when other organisations install your addon. They approve it on a consent screen that names you and lists the scopes, and your addon receives the token directly.

Use a hand-minted token when the integration is for your own organisation only. See [Authentication](/developers/docs/authentication).

## 1. Register a client

**Addons → Develop → your listing → OAuth client.** Add every redirect URI you use and create the client.

The client secret is shown once. Redirect URIs are matched exactly, so `https://app.example/cb` will not accept `https://app.example/cb/done`. HTTPS only, except `localhost` and `127.0.0.1` for development.

## 2. Send the member to the consent screen

```http
GET /oauth/authorize
  ?client_id=ptc_…
  &redirect_uri=https://app.example/cb
  &response_type=code
  &scope=projects:read specs:read
  &state=<random per request>
```

`state` is yours to generate and check on the way back. Only an org admin can approve, and only scopes the API defines are accepted.

For a public client, add `code_challenge` and `code_challenge_method=S256`.

## 3. Handle the redirect

On approval the member returns to your redirect URI with `code` and your `state`.

```
https://app.example/cb?code=ptac_…&state=<yours>
```

On refusal you get `error=access_denied` and the same `state`. Compare the returned `state` to the one you issued before doing anything else.

## 4. Exchange the code

```bash
curl -X POST https://<your-host>/api/v1/oauth/token \
  -d grant_type=authorization_code \
  -d code=ptac_… \
  -d redirect_uri=https://app.example/cb \
  -d client_id=ptc_… \
  -d client_secret=ptcs_…
```

```json
{ "access_token": "ptk_live_…", "token_type": "Bearer", "scope": "projects:read specs:read" }
```

Codes are single use and expire in minutes. `redirect_uri` must match the one you authorized with. With PKCE, send `code_verifier` instead of `client_secret`.

The token works on every `/api/v1` endpoint exactly like a hand-minted one.

## Errors

Failures return an OAuth error object with a `400`, or `401` for client authentication.

| `error` | Means |
| --- | --- |
| `invalid_client` | Unknown client, or the secret did not match |
| `invalid_grant` | Code expired, already used, issued to another client, or the redirect did not match |
| `invalid_request` | A required parameter is missing |
| `unsupported_grant_type` | Only `authorization_code` is supported |
| `access_denied` | The member cancelled |

A `429` means you are sending too fast. Honour the `Retry-After` header and back off rather than retrying immediately.

## Revocation

An admin can revoke a grant from **Admin → Integrations → Authorized addons**. That immediately kills every token the grant issued, and calls start returning `401`.

Treat a sudden `401` as a revoked grant and start the flow again rather than retrying.

## Before you publish

Publishing requires a contact email and acceptance of the [publisher agreement](/developers/agreement). The email is private, and we use it when your addon starts failing or gets reported.
