Penneo OAuth API

The OAuth API from Penneo — 3 operation(s) for oauth.

Operations 3

GET /oauth/authorize Authenticate a user with Penneo #
POST /oauth/revoke Revoke a refresh_token #
POST /oauth/token Obtain or refresh an access_token #

Specifications

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/penneo:penneo-oauth-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

penneo-oauth-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Penneo Authentication OAuth API
  version: v1
  description: 'This page allows you to see the requests, parameters, and responses available from our API.


    You will need an account to use these endpoints, you can get a trial account by visiting penneo.com.


    Please also read the documentation before you begin.'
  contact:
    name: Penneo Support
    url: https://www.support.penneo.com
servers:
- url: https://login-sandbox.penneo.com/
  description: Sandbox server (uses your test data)
- url: https://login.penneo.com/
  description: Production server (uses your live data)
tags:
- name: OAuth
paths:
  /oauth/authorize:
    get:
      summary: Authenticate a user with Penneo
      description: 'The browser will redirect the user to the Penneo Login page and, after a successful login, back to the

        specified `redirect_uri`.


        A `code` parameter will be attached to the request so it can be later exchanged for an `access_token`.'
      tags:
      - OAuth
      operationId: authorizeUser
      parameters:
      - name: response_type
        in: query
        required: true
        schema:
          type: string
          enum:
          - code
      - name: redirect_uri
        in: query
        required: true
        schema:
          type: string
          format: uri
          example: https://your.domain/callback
        description: 'The URL to which Penneo will redirect the browser after authorization has been granted by the user.<br>

          It must be first registered in the client configuration.

          '
      - name: client_id
        in: query
        required: true
        schema:
          type: string
        description: The alphanumeric `client_id` obtained after registering your client application.
      - name: code_challenge
        in: query
        required: false
        schema:
          type: string
        description: 'Value derived from code verifier created in compliance with PKCE specification.<br>

          See [RFC 7636](https://www.rfc-editor.org/rfc/rfc7636) for more info.<br>

          **Required if the client is configured to require PKCE**, otherwise optional but strongly recommended

          for enhanced security. If provided, you must also include `code_verifier` when exchanging the

          authorization code for tokens.

          '
      - name: code_challenge_method
        in: query
        required: false
        schema:
          type: string
          enum:
          - plain
          - S256
          default: plain
        description: 'Name of the algorithm used to calculate `code_challenge`. <br>

          Required only if `code_challenge` is provided.<br>

          Clients are **strongly recommended** to use `S256`, as it will not only preventing

          [CSRF](https://en.wikipedia.org/wiki/Cross-site_request_forgery) but also mitigate

          [eavesdropping](https://en.wikipedia.org/wiki/Network_eavesdropping) and

          [man-in-the-middle](https://en.wikipedia.org/wiki/Man-in-the-middle_attack) attacks.

          '
      - name: state
        in: query
        required: false
        schema:
          type: string
        description: 'An opaque value of your choice that will be included when redirecting back to your client application.<br>

          This value is not required but **strongly recommended** to improve security and to prevent

          [CSRF](https://en.wikipedia.org/wiki/Cross-site_request_forgery) attacks.<br> See [RFC-6749, Section

          10.12](https://www.rfc-editor.org/rfc/rfc6749#section-10.12) for more info.

          '
      responses:
        '302':
          description: Found
          headers:
            Location:
              schema:
                format: uri
                type: string
                description: 'Redirect to the specified redirect_uri with a `code` and `scope` parameters attached to the query

                  '
                example: '''https://your.domain/callback?state=<your state value>&code=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...''

                  '
        '400':
          description: Bad Request
          content:
            text/html:
              schema:
                type: string
              example: Missing required parameter
        '403':
          description: Unauthorized
          content:
            text/html:
              schema:
                type: string
              example: Unauthorized client
        '500':
          description: Internal Server Error
          content:
            text/html:
              schema:
                type: string
              example: Redirect URI does not match any of those defined for the client
        '501':
          description: Not Implemented
          content:
            text/html:
              schema:
                type: string
              example: Unsupported response type
  /oauth/revoke:
    post:
      summary: Revoke a refresh_token
      description: 'Revokes a `refresh_token` (RFC 7009). The confidential client must authenticate with

        `client_id` and `client_secret` in the body or via `Authorization: Basic` (RFC 6749 §2.3.1).

        The token is removed only if it was issued to that client; otherwise the response is still 200.'
      tags:
      - OAuth
      operationId: revokeRefreshToken
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                token:
                  type: string
                client_id:
                  type: string
                  description: May be sent in the body or using an HTTP Basic Authorization header (RFC 6749).
                client_secret:
                  type: string
                  description: May be sent in the body or using an HTTP Basic Authorization header (RFC 6749).
              required:
              - token
              - client_id
              - client_secret
      responses:
        '200':
          description: OK | The refresh token is revoked, invalid, doesn't exist or was not issued for the client.
        '400':
          description: Bad Request | Missing required parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: invalid_request
                error_description: Invalid request
        '401':
          description: Unauthorized | Invalid client credentials
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: invalid_client
                error_description: Invalid client ID or secret
        '500':
          $ref: '#/components/responses/InternalServerError'
  /oauth/token:
    post:
      summary: Obtain or refresh an access_token
      description: Obtain a new `access_token` by exchanging an authorization `code`, a `refresh_token` or `api_keys`.
      tags:
      - OAuth
      operationId: exchangeToken
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                grant_type:
                  type: string
                  enum:
                  - authorization_code
                  - refresh_token
                  - api_keys
                redirect_uri:
                  type: string
                  format: uri
                  example: https://your.domain/callback
                  description: 'Required only if using the Authorization Code grant.

                    The registered callback URI.<br>

                    It''s the same URI used in the `POST /oauth/authorize` request.

                    '
                refresh_token:
                  type: string
                  description: 'Required only if using the Refresh Token Grant.<br>

                    Use the value of the previously stored `refresh_token`.

                    '
                client_id:
                  type: string
                  description: 'The alphanumeric `client_id` (may be sent in the body or via `Authorization: Basic`).

                    '
                client_secret:
                  type: string
                  description: 'The alphanumeric `client_secret` (may be sent in the body or via `Authorization: Basic`).

                    '
                scope:
                  type: string
                  description: 'A list of space-separated scopes that your application will have access to on behalf of the user.

                    '
                code:
                  type: string
                  description: 'Required only if using the Authorization Code Grant.<br>

                    Use the value of the `code` parameter attached to callback request.

                    '
                code_verifier:
                  type: string
                  description: 'Required only for Authorization Code Grant when PKCE is used.<br>

                    If `code_challenge` was provided in the `/oauth/authorize` request, you must include

                    the original `code_verifier` value here. If PKCE was not used (no `code_challenge` in

                    the authorization request), this parameter should be omitted.

                    '
                key:
                  type: string
                  description: 'Required only for the `api_keys` grant.<br>

                    API Key generated in Penneo user settings.

                    '
                digest:
                  type: string
                  description: 'Required only for the `api_keys` grant.<br>

                    Concatenation of the raw nonce, timestamp, and API secret, then *SHA-1 hashed*, and finally

                    *Base64-encoded*. The nonce, while sent as a Base64-encoded string, is used in its raw form to

                    generate this digest.

                    '
                nonce:
                  type: string
                  description: 'Required only for the `api_keys` grant. Used to generate the `digest` parameter.<br>

                    A randomly generated string, no longer than 64 characters. It should be sent *Base64-encoded*.

                    '
                created_at:
                  type: string
                  description: 'Required only for the `api_keys` grant. Used to generate the `digest` parameter.<br>

                    Current date and time in UTC or including timezone. Most standard formats are supported.

                    A maximum clock skew of up to 60 seconds is allowed (in plus or minus).

                    '
              required:
              - grant_type
              description: 'Include `client_id` and `client_secret` in the body, **or** authenticate the client with

                `Authorization: Basic` (Base64 of `client_id:client_secret`) per RFC 6749 §2.3.1.

                '
            examples:
              code:
                summary: Authorization Code Grant (with PKCE)
                description: 'Example using PKCE. If you did not use `code_challenge` in the authorization request,

                  omit the `code_verifier` parameter.

                  '
                value:
                  grant_type: authorization_code
                  code: string
                  redirect_uri: https://your.domain/callback
                  client_id: string
                  client_secret: string
                  code_verifier: string
              codeWithoutPKCE:
                summary: Authorization Code Grant (without PKCE)
                description: 'Example without PKCE. Use this if you did not provide `code_challenge` in the authorization request.

                  '
                value:
                  grant_type: authorization_code
                  code: string
                  redirect_uri: https://your.domain/callback
                  client_id: string
                  client_secret: string
              refresh_token:
                summary: Refresh Token Grant
                value:
                  grant_type: refresh_token
                  refresh_token: string
                  redirect_uri: https://your.domain/callback
                  client_id: string
                  client_secret: string
              api_keys:
                summary: Api Keys Grant
                description: 'This is a custom grant type, meant to exchange API keys for an access token for programmatic access.

                  Beware that this grant type does not issue refresh tokens. If your token has expired you will have to

                  use the `api_keys` again to obtain a new one.

                  '
                value:
                  grant_type: api_keys
                  scope: read
                  key: paadeeesmmmdda
                  client_id: d7898z66d91a56e6ee39d
                  client_secret: taadtaadpstcsm
                  digest: quR/EWLAV4xLf9Zqyw4pDmfV9OY=
                  nonce: d36e316282959a9ed4c89851497a717f
                  timestamp: '2003-12-15T14:43:07Z'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                oneOf:
                - $ref: '#/components/schemas/Token'
                - $ref: '#/components/schemas/TokenWithoutRefreshToken'
              examples:
                withRefreshToken:
                  summary: Access token and refresh token, which can be used to request a new access token.
                  value:
                    access_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
                    access_token_expires_at: 1697703152
                    expires_at: '1234567890'
                    refresh_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
                    refresh_token_expires_at: 1698703152
                    token_type: bearer
                withoutRefreshToken:
                  summary: api_keys grant
                  description: "The response for the `api_keys` grant does not include a refresh token. If your token has expired\n you will have to use the `api_keys` again to obtain a new one.\n"
                  value:
                    access_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
                    access_token_expires_at: 1697703152
                    expires_at: '1234567890'
                    token_type: bearer
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                Invalid authorization code:
                  value:
                    error: invalid_grant
                    error_description: Invalid Authorization Code
                Invalid redirect URI:
                  value:
                    error: invalid_grant
                    error_description: Redirect URI does not match
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                Invalid Client ID or Secret:
                  value:
                    error: invalid_client
                    error_description: Invalid client ID or secret
                Invalid Client ID:
                  value:
                    error: invalid_client
                    error_description: Client ID does not match
                Invalid refresh token:
                  value:
                    error: invalid_token
                    error_description: Invalid refresh token
                Expired refresh token:
                  value:
                    error: invalid_token
                    error_description: Refresh token expired
                Missing code verifier (when PKCE was used):
                  value:
                    error: invalid_grant
                    error_description: Missing PKCE code verifier
                Invalid code verifier:
                  value:
                    error: invalid_grant
                    error_description: Invalid PKCE code verifier
                Code verifier provided but no challenge was used:
                  value:
                    error: invalid_grant
                    error_description: PKCE code verifier provided but no code challenge was used
        '500':
          $ref: '#/components/responses/InternalServerError'
components:
  schemas:
    Error:
      type: object
      properties:
        error:
          type: string
        code:
          type: integer
        details:
          type: array
          items:
            type: object
    Token:
      type: object
      allOf:
      - $ref: '#/components/schemas/TokenWithoutRefreshToken'
      - type: object
        properties:
          refresh_token:
            type: string
          refresh_token_expires_at:
            type: number
            format: unix timestamp
    TokenWithoutRefreshToken:
      type: object
      properties:
        access_token:
          type: string
        access_token_expires_at:
          type: number
          format: unix timestamp
        expires_in:
          type: string
        token_type:
          type: string
  responses:
    InternalServerError:
      description: Internal Server Error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: Unexpected server error occurred. If problem persists please contact the administrator.
            code: 500
externalDocs:
  url: https://penneo.readme.io/docs/
  description: Penneo API documentation
x-provenance:
  method: searched
  source: https://developer.penneo.com/reference
  harvested: '2026-10-27'
  tool: harvest-readme-openapi.py
  note: Assembled verbatim from the OpenAPI definition ReadMe embeds in each operation page of the provider's developer portal; paths and components merged by API title, nothing authored.
  assembled_from:
  - https://developer.penneo.com/reference/get-version.md
  - https://developer.penneo.com/reference/oauth-authenticate-user.md
  - https://developer.penneo.com/reference/oauth-refresh-token.md
  - https://developer.penneo.com/reference/oauth-revoke-refresh-token.md
  merge:
    pages: 4
    deduped: 4
    conflicts_kept_first: 0