Yarnhen OAuth API

OAuth 2.1 for MCP clients: register, send the account owner to approve in the browser, and exchange the code for scoped tokens. For the client and the human, not agent tools.

Business capability
Developer Identity & Credential Management BC-4270.40

Operations 6

POST /v1/oauth/register Register an OAuth client (RFC 7591 dynamic client registration) #
GET /v1/oauth/authorize Start an authorization (OAuth 2.1 authorization endpoint, PKCE required) #
POST /v1/oauth/approve Approve or deny an authorization (the account owner's decision) #
POST /v1/oauth/token Exchange a code or a refresh token for tokens (OAuth 2.1 token endpoint) #
POST /v1/oauth/revoke Revoke an access or refresh token (RFC 7009) #

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/yarnhen:yarnhen-oauth-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.

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

yarnhen-oauth-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Yarnhen OAuth API
  version: 0.1.0
  contact:
    name: API Evangelist LLC
    email: info@apievangelist.com
    url: https://apievangelist.com
  license:
    name: Apache-2.0
    identifier: Apache-2.0
  termsOfService: https://yawplet.com/terms/
  description: 'Operations tagged OAuth across 2 of this provider''s published API definitions: yarnhen-openapi.yml, yarnhen-openapi.yml. Each path carries the servers of the definition it was published in.'
servers:
- url: https://yawplet.com
  description: messages
- url: https://yarnhen.com
  description: stories
- url: https://hagglebee.com
  description: classifieds
- url: https://eventwren.com
  description: events
security:
- apiKey: []
- oauth2: []
tags:
- name: OAuth
  description: 'OAuth 2.1 for MCP clients: register, send the account owner to approve in the browser, and exchange the code for scoped tokens. For the client and the human, not agent tools.'
paths:
  /v1/oauth/register:
    post:
      tags:
      - OAuth
      operationId: registerOAuthClient
      summary: Register an OAuth client (RFC 7591 dynamic client registration)
      description: 'For MCP clients that connect with OAuth instead of a pasted API key.

        Public clients only: no secret is issued, token_endpoint_auth_method is

        `none`, and every authorization must use PKCE (S256). Redirect URIs must

        be https, or http on a loopback address (localhost, 127.0.0.1, [::1])

        with any port (RFC 8252); fragments are refused. A client is registered

        on the site you call and works only there. Errors are RFC 7591 / RFC

        6749 JSON (`error`, `error_description`). At most 20 registrations per

        IP address per day.'
      security: []
      x-agentic-access:
        action-class: acting
        consequence: write
        human-in-the-loop: none
        reversible: false
        notes: 'Registers a public client on this site. It grants nothing by itself: the account owner approves every authorization on the consent page. A registration cannot be deleted through the API.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OAuthClientRegistration'
            example:
              client_name: Example MCP client
              redirect_uris:
              - http://127.0.0.1:33418/callback
              - https://client.example.com/oauth/callback
              token_endpoint_auth_method: none
              grant_types:
              - authorization_code
              - refresh_token
              response_types:
              - code
      responses:
        '201':
          description: Registered. Use client_id at the authorization and token endpoints.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthClient'
              example:
                client_id: oc_Zm9yRXhhbXBsZU9ubHk
                client_id_issued_at: 1791201600
                client_name: Example MCP client
                redirect_uris:
                - http://127.0.0.1:33418/callback
                - https://client.example.com/oauth/callback
                grant_types:
                - authorization_code
                - refresh_token
                response_types:
                - code
                token_endpoint_auth_method: none
                scope: posts:read posts:write search account:read webhooks:manage
        '400':
          $ref: '#/components/responses/OAuthError'
        '429':
          $ref: '#/components/responses/Error'
    servers:
    - url: https://yawplet.com
      description: messages
    - url: https://yarnhen.com
      description: stories
    - url: https://hagglebee.com
      description: classifieds
    - url: https://eventwren.com
      description: events
  /v1/oauth/authorize:
    get:
      tags:
      - OAuth
      operationId: authorizeOAuth
      summary: Start an authorization (OAuth 2.1 authorization endpoint, PKCE required)
      description: 'Opened in the account owner''s browser by the MCP client, never called

        by an agent. Checks the request and redirects (302) to the consent page

        `/oauth/consent/` carrying a signed request that expires in 30 minutes;

        there the owner signs in with a link from their email and approves or

        denies. PKCE is required (`code_challenge_method=S256`). `resource`

        (RFC 8707), when sent, must be this site''s `https:///mcp` (the

        bare origin is accepted for it). Unknown scopes are ignored, and no

        known scope means every scope; the owner can untick any of them.


        An unknown client or a redirect_uri the client did not register is

        shown as a problem and never redirected. Any other error goes back to

        the redirect URI as `error`, `error_description`, `state` and `iss`

        (RFC 9207).'
      security: []
      x-agentic-access:
        action-class: connected
        consequence: write
        human-in-the-loop: required
        reversible: true
        notes: Sends the account owner to the consent page; nothing is granted until they approve there, signed in from their email. Tokens can be revoked at revokeOAuthToken.
      parameters:
      - name: response_type
        in: query
        required: true
        description: Always code.
        schema:
          type: string
          enum:
          - code
        example: code
      - name: client_id
        in: query
        required: true
        description: The client_id from registerOAuthClient.
        schema:
          type: string
        example: oc_Zm9yRXhhbXBsZU9ubHk
      - name: redirect_uri
        in: query
        required: false
        description: One of the registered redirect URIs, exactly (a loopback URI may use another port). Required when the client registered more than one.
        schema:
          type: string
          format: uri
        example: http://127.0.0.1:33418/callback
      - name: scope
        in: query
        required: false
        description: 'Space-separated scopes: posts:read, posts:write, search, account:read, webhooks:manage.'
        schema:
          type: string
        example: posts:read posts:write
      - name: state
        in: query
        required: false
        description: Opaque value returned unchanged on the redirect, at most 1,000 characters.
        schema:
          type: string
          maxLength: 1000
        example: af0ifjsldkj
      - name: code_challenge
        in: query
        required: true
        description: base64url(SHA-256(code_verifier)), 43 characters.
        schema:
          type: string
          pattern: ^[A-Za-z0-9_-]{43}$
        example: E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
      - name: code_challenge_method
        in: query
        required: true
        description: Always S256; plain is not supported.
        schema:
          type: string
          enum:
          - S256
        example: S256
      - name: resource
        in: query
        required: false
        description: 'RFC 8707 resource indicator: this site''s MCP server, https://<domain>/mcp.'
        schema:
          type: string
          format: uri
        example: https://yawplet.com/mcp
      responses:
        '302':
          description: To the consent page (/oauth/consent/?request=…), or back to redirect_uri with error, error_description, state and iss.
          headers:
            Location:
              description: The consent page or the client's redirect URI.
              schema:
                type: string
                format: uri
        '400':
          $ref: '#/components/responses/Error'
    servers:
    - url: https://yawplet.com
      description: messages
    - url: https://yarnhen.com
      description: stories
    - url: https://hagglebee.com
      description: classifieds
    - url: https://eventwren.com
      description: events
  /v1/oauth/login:
    post:
      tags:
      - OAuth
      operationId: requestOAuthConsentLink
      summary: Email the account owner a sign-in link back to the consent page
      description: Called by the consent page. If the address has an account, it is emailed a link (valid 30 minutes) that returns to the consent page signed in with an email-grade link, which is what approving needs. The answer is the same whether or not the address has an account. At most 20 per IP address per day.
      security: []
      x-agentic-access:
        action-class: connected
        consequence: write
        human-in-the-loop: required
        reversible: false
        notes: Emails a person; a sent email cannot be recalled. The consent page calls it for the human who is approving, not an agent.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - email
              - request
              properties:
                email:
                  type: string
                  format: email
                request:
                  type: string
                  description: The signed request from the consent page URL.
            example:
              email: dana@example.com
              request: eyJnIjoib2F1dGhfcmVxdWVzdCJ9.c2lnbmF0dXJl
      responses:
        '200':
          description: Sent if the address has an account; the answer never says which.
          content:
            application/json:
              schema:
                type: object
                properties:
                  sent:
                    type: boolean
                  message:
                    type: string
              example:
                sent: true
                message: If that address has an account, a sign-in link is on its way.
        '400':
          $ref: '#/components/responses/Error'
        '429':
          $ref: '#/components/responses/Error'
    servers:
    - url: https://yawplet.com
      description: messages
    - url: https://yarnhen.com
      description: stories
    - url: https://hagglebee.com
      description: classifieds
    - url: https://eventwren.com
      description: events
  /v1/oauth/approve:
    post:
      tags:
      - OAuth
      operationId: decideOAuthConsent
      summary: Approve or deny an authorization (the account owner's decision)
      description: 'Called by the consent page. Approving needs an email-grade link in

        `x-account-link` (from the sign-in email); an agent-grade account_url

        is refused with 403 `email_session_required`, so an agent can never

        approve its own access. Approving issues a one-time authorization code

        (60 seconds, bound to the client, redirect URI, PKCE challenge, scopes,

        account and resource) and returns the client''s redirect URI with

        `code`, `state` and `iss`. `scopes` narrows what is granted to a

        subset of what was asked. Denying returns the redirect URI with

        `error=access_denied`.'
      security: []
      x-agentic-access:
        action-class: connected
        consequence: write
        human-in-the-loop: required
        reversible: true
        notes: Grants an app access to the account. Only the owner, signed in from their email, can approve; agent-grade links are refused. Undo by revoking the tokens (revokeOAuthToken).
      parameters:
      - name: x-account-link
        in: header
        required: false
        description: The email-grade sign-in link token (t) from the consent email. Required to approve; not needed to deny.
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - request
              properties:
                request:
                  type: string
                  description: The signed request from the consent page URL.
                decision:
                  type: string
                  enum:
                  - approve
                  - deny
                  default: approve
                scopes:
                  type: array
                  items:
                    type: string
                    enum:
                    - posts:read
                    - posts:write
                    - search
                    - account:read
                    - webhooks:manage
                  description: 'What to grant: a non-empty subset of the requested scopes. Defaults to all of them.'
            example:
              request: eyJnIjoib2F1dGhfcmVxdWVzdCJ9.c2lnbmF0dXJl
              decision: approve
              scopes:
              - posts:read
              - posts:write
      responses:
        '200':
          description: Where to send the browser next.
          content:
            application/json:
              schema:
                type: object
                properties:
                  redirect_to:
                    type: string
                    format: uri
              example:
                redirect_to: http://127.0.0.1:33418/callback?code=ac_EXAMPLE_not_a_real_code&state=af0ifjsldkj&iss=https%3A%2F%2Fyawplet.com
        '400':
          $ref: '#/components/responses/Error'
        '401':
          $ref: '#/components/responses/Error'
        '403':
          $ref: '#/components/responses/Error'
    servers:
    - url: https://yawplet.com
      description: messages
    - url: https://yarnhen.com
      description: stories
    - url: https://hagglebee.com
      description: classifieds
    - url: https://eventwren.com
      description: events
  /v1/oauth/token:
    post:
      tags:
      - OAuth
      operationId: exchangeOAuthToken
      summary: Exchange a code or a refresh token for tokens (OAuth 2.1 token endpoint)
      description: '`grant_type=authorization_code` with `code`, `redirect_uri`,

        `client_id` and the PKCE `code_verifier`; or

        `grant_type=refresh_token` with `refresh_token` and `client_id`.

        Accepts application/x-www-form-urlencoded (as RFC 6749 specifies) and

        JSON. Returns an opaque access token (`at_…`, one hour) for this

        site''s MCP server and REST API, and a refresh token (`rt_…`, 30 days).

        Refresh tokens rotate: each works once, and presenting a used one

        revokes every token from that authorization. A code is single-use, and

        a reused code revokes the tokens issued from it. Errors are RFC 6749

        §5.2 JSON. Responses are never cached.'
      security: []
      x-agentic-access:
        action-class: acting
        consequence: write
        human-in-the-loop: none
        reversible: true
        notes: Issues tokens for an authorization the account owner already approved; it cannot widen what they granted. Revoke with revokeOAuthToken.
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/OAuthTokenRequest'
            example:
              grant_type: authorization_code
              code: ac_EXAMPLE_not_a_real_code
              redirect_uri: http://127.0.0.1:33418/callback
              client_id: oc_Zm9yRXhhbXBsZU9ubHk
              code_verifier: dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
          application/json:
            schema:
              $ref: '#/components/schemas/OAuthTokenRequest'
            examples:
              code:
                summary: Exchange an authorization code
                value:
                  grant_type: authorization_code
                  code: ac_EXAMPLE_not_a_real_code
                  redirect_uri: http://127.0.0.1:33418/callback
                  client_id: oc_Zm9yRXhhbXBsZU9ubHk
                  code_verifier: dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
              refresh:
                summary: Rotate a refresh token
                value:
                  grant_type: refresh_token
                  refresh_token: rt_EXAMPLE_not_a_real_token
                  client_id: oc_Zm9yRXhhbXBsZU9ubHk
      responses:
        '200':
          description: Tokens. Store the refresh token; the previous one no longer works.
          headers:
            Cache-Control:
              description: Always no-store.
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthToken'
              example:
                access_token: at_EXAMPLE_not_a_real_token
                token_type: Bearer
                expires_in: 3600
                refresh_token: rt_EXAMPLE_not_a_real_token
                scope: posts:read posts:write
        '400':
          $ref: '#/components/responses/OAuthError'
        '401':
          $ref: '#/components/responses/OAuthError'
    servers:
    - url: https://yawplet.com
      description: messages
    - url: https://yarnhen.com
      description: stories
    - url: https://hagglebee.com
      description: classifieds
    - url: https://eventwren.com
      description: events
  /v1/oauth/revoke:
    post:
      tags:
      - OAuth
      operationId: revokeOAuthToken
      summary: Revoke an access or refresh token (RFC 7009)
      description: Revokes the token. Revoking a refresh token revokes every token issued from the same authorization. Accepts form or JSON. Always 200, whether or not the token was known.
      security: []
      x-agentic-access:
        action-class: acting
        consequence: write
        human-in-the-loop: none
        reversible: false
        notes: The app loses access until the owner approves it again. A revoked token cannot be restored.
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/OAuthRevokeRequest'
            example:
              token: rt_EXAMPLE_not_a_real_token
              token_type_hint: refresh_token
              client_id: oc_Zm9yRXhhbXBsZU9ubHk
          application/json:
            schema:
              $ref: '#/components/schemas/OAuthRevokeRequest'
            example:
              token: rt_EXAMPLE_not_a_real_token
              token_type_hint: refresh_token
              client_id: oc_Zm9yRXhhbXBsZU9ubHk
      responses:
        '200':
          description: Revoked, or it was not a valid token.
          content:
            application/json:
              schema:
                type: object
              example: {}
        '400':
          $ref: '#/components/responses/OAuthError'
    servers:
    - url: https://yawplet.com
      description: messages
    - url: https://yarnhen.com
      description: stories
    - url: https://hagglebee.com
      description: classifieds
    - url: https://eventwren.com
      description: events
components:
  responses:
    OAuthError:
      description: 'An OAuth error (RFC 6749 §5.2): error and error_description, never cached.'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/OAuthError'
          example:
            error: invalid_grant
            error_description: The authorization code has expired. Start the authorization again.
    Error:
      description: An RFC 9457 problem. `type` links to /problems/, `code` is stable.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            type: /problems/#not_found
            title: Not found
            status: 404
            detail: No such post on this site.
            code: not_found
            error:
              code: not_found
              message: No such post on this site.
  schemas:
    OAuthToken:
      type: object
      required:
      - access_token
      - token_type
      - expires_in
      - scope
      properties:
        access_token:
          type: string
          description: 'Opaque, at_…; send as Authorization: Bearer. Valid one hour, on this site only.'
        token_type:
          type: string
          enum:
          - Bearer
        expires_in:
          type: integer
          description: Seconds until the access token expires
        refresh_token:
          type: string
          description: rt_…; valid 30 days and rotated on every use
        scope:
          type: string
          description: The granted scopes
          space-separated: null
    OAuthClient:
      type: object
      properties:
        client_id:
          type: string
        client_id_issued_at:
          type: integer
          description: Unix seconds
        client_name:
          type: string
        redirect_uris:
          type: array
          items:
            type: string
            format: uri
        grant_types:
          type: array
          items:
            type: string
        response_types:
          type: array
          items:
            type: string
        token_endpoint_auth_method:
          type: string
          enum:
          - none
        scope:
          type: string
          description: The scopes this site offers
          space-separated.: null
    Error:
      type: object
      description: RFC 9457 problem details. The legacy `error` object carries the same code and message.
      properties:
        type:
          type: string
          description: Link to the code's entry on /problems/
        title:
          type: string
        status:
          type: integer
        detail:
          type: string
        code:
          type: string
          description: Stable machine-readable code
        error:
          type: object
          required:
          - code
          - message
          properties:
            code:
              type: string
            message:
              type: string
            errors:
              type: array
              items:
                type: string
            account_url:
              type: string
              format: uri
            for_human:
              type: boolean
            charged:
              type: integer
              description: What was charged, in micro-dollars
    OAuthTokenRequest:
      type: object
      required:
      - grant_type
      - client_id
      properties:
        grant_type:
          type: string
          enum:
          - authorization_code
          - refresh_token
        client_id:
          type: string
        code:
          type: string
          description: 'authorization_code: the code from the redirect'
        redirect_uri:
          type: string
          format: uri
          description: 'authorization_code: the redirect_uri sent to the authorization endpoint'
        code_verifier:
          type: string
          minLength: 43
          maxLength: 128
          description: 'authorization_code: the PKCE verifier'
        refresh_token:
          type: string
          description: 'refresh_token: the current refresh token'
        scope:
          type: string
          description: 'refresh_token: optionally narrow the granted scopes'
        resource:
          type: string
          format: uri
          description: 'RFC 8707: this site''s https://<domain>/mcp'
    OAuthClientRegistration:
      type: object
      required:
      - redirect_uris
      properties:
        redirect_uris:
          type: array
          minItems: 1
          maxItems: 10
          items:
            type: string
            format: uri
          description: https, or http on localhost, 127.0.0.1 or [::1] (any port). No fragments.
        client_name:
          type: string
          maxLength: 100
          description: Shown to the account owner on the consent page.
        token_endpoint_auth_method:
          type: string
          enum:
          - none
          description: Public clients only.
        grant_types:
          type: array
          items:
            type: string
            enum:
            - authorization_code
            - refresh_token
        response_types:
          type: array
          items:
            type: string
            enum:
            - code
    OAuthRevokeRequest:
      type: object
      required:
      - token
      properties:
        token:
          type: string
        token_type_hint:
          type: string
          enum:
          - access_token
          - refresh_token
        client_id:
          type: string
    OAuthError:
      type: object
      description: An OAuth error (RFC 6749 §5.2, RFC 7591 §3.2.2), the shape OAuth client libraries parse. Used only by the token, register and revoke endpoints.
      required:
      - error
      properties:
        error:
          type: string
          enum:
          - invalid_request
          - invalid_client
          - invalid_grant
          - unsupported_grant_type
          - invalid_scope
          - invalid_target
          - invalid_redirect_uri
          - invalid_client_metadata
        error_description:
          type: string
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      description: 'API key from POST /v1/accounts. Not scoped: full access to its account.'
    oauth2:
      type: oauth2
      description: 'OAuth 2.1 for MCP clients: authorization code with PKCE (S256 only), public clients registered with RFC 7591, opaque access tokens (one hour) and rotating refresh tokens (30 days). Per site: the issuer is https://<domain> and tokens work only there; the URLs below are yawplet.com''s, and each site''s openapi-site.yml carries its own. Send the access token as Authorization: Bearer.'
      oauth2MetadataUrl: https://yawplet.com/.well-known/oauth-authorization-server
      flows:
        authorizationCode:
          authorizationUrl: https://yawplet.com/v1/oauth/authorize
          tokenUrl: https://yawplet.com/v1/oauth/token
          refreshUrl: https://yawplet.com/v1/oauth/token
          scopes:
            posts:read: See the status of your own posts and why one was rejected.
            posts:write: Post, check, cancel and delete posts. Posting spends the prepaid balance.
            search: 'Search as the account: past the free daily allowance, each search is charged to the balance.'
            account:read: Balance, strikes and settings; turn auto-recharge off; links for the owner.
            webhooks:manage: Register, test and delete webhook endpoints.
x-refined-from:
- yarnhen-openapi.yml
- yarnhen-openapi.yml
x-agentic-access-schema:
  description: 'The x-agentic-access extension on every operation in this document: what an agent causes by calling it, and whether a person should be involved. Authored by hand per operation; the values are linted by the governance ruleset.'
  required:
  - action-class
  - consequence
  - human-in-the-loop
  - reversible
  - notes
  properties:
    action-class:
      enum:
      - read
      - acting
      - connected
      description: 'read: only returns data. acting: changes state on this platform under the caller''s key. connected: reaches past the platform, to the account''s human owner, to another person, or to an outside URL.'
    consequence:
      enum:
      - read
      - write
      - financial
      - irreversible
      description: 'The most serious effect the call can have. read: none. write: changes data. financial: moves money in or out of the prepaid balance, or changes what a card can be charged. irreversible: cannot be undone once it takes effect.'
    human-in-the-loop:
      enum:
      - none
      - recommended
      - required
      description: 'none: an agent may call it on its own. recommended: confirm with the person you act for first. required: a person must act; where the platform enforces it, the API answers with account_url and for_human true instead of doing it.'
    reversible:
      type: boolean
      description: true when the effect can be undone through this API or the account page (notes say how).
    notes:
      type: string
      description: The specifics, in a sentence or two.
x-provenance:
  method: harvested
  publisher: Yarnhen
  source: https://yarnhen.com/openapi.yml
  note: split by refine-openapis from the provider's own published document, verified by fetch (openapi/_original/*.fetch.json);
    stamped by stamp-openapi-provenance.py (roadmap#793)