Grow LMS API Documentation

Grow by Plenum — Authentication & Access Setup

How your systems securely connect to your Grow environment.

This document is for the engineers who will build the connection between your systems and Grow by Plenum. It explains how access is granted, how credentials are issued and scoped, what a token’s lifecycle looks like in practice, and how to handle the edge cases. It assumes familiarity with OAuth2 and HTTP APIs.

Throughout, <SCHOOL_ID> is the hostname of your dedicated Grow environment (for example, your-org.plenumapps.com). <CLIENT_ID>, <CLIENT_SECRET>, and <ACCESS_TOKEN> are the credentials Plenum issues to you during onboarding. No real secret appears in this document.


1. The short version

Grow’s API uses OAuth2. Your system exchanges a client ID and client secret for an access token, then presents that token on every subsequent request. Two operational characteristics are worth knowing immediately, because they make integration simpler than most teams expect:

  • Credentials are scoped per environment. The credentials Plenum issues you authenticate to your <SCHOOL_ID> and nothing else. There is no shared, cross-tenant key.
  • Tokens are long-lived in practice. Although the protocol returns a standard “seconds until expiry” value, on Plenum-managed environments that window is approximately one year — not the short-lived window many OAuth2 integrations assume (see Section 4). For most integrations this means token management is close to a non-issue.

2. Grant types

Grow supports three OAuth2 grant types. Which one you use depends on what you’re building.

Grant type Use it for Who acts
Client credentials Machine-to-machine integration — your data warehouse, a scheduled extract, a sync service, a reconciliation job. This is the grant most data integrations use. Your server, on its own authority
Resource-owner password A trusted first-party application that signs an individual user in directly with their own credentials. Your app, on behalf of a specific user
Refresh token Renewing access obtained via the password grant, without re-prompting the user. Your app, automatically

For the typical Turner-style integration — pulling completion and certificate data into an enterprise data platform, or receiving real-time events — you’ll use the client credentials grant. The rest of this document focuses there, and notes where the others differ.

Security note for the password grant. If you implement the resource-owner password grant, route the token request through a server-side proxy rather than embedding the client secret in a client application. Your app collects the user’s username and password, hands them to your backend, and your backend attaches the client ID and secret before forwarding the request to Grow. This keeps the secret off the end-user device.


3. Requesting an access token

Send a POST to the token endpoint on your environment. Every request to Grow — including this token request itself — must carry a client-identifier header, Pl-Client, set to your client ID. The body specifies the grant. (Plenum confirms your exact credential values at onboarding.)

Request (client credentials grant):

curl "https://<SCHOOL_ID>/admin/api/oauth2/access_token" \
  -H "Pl-Client: <CLIENT_ID>" \
  -d data='{
    "client_id": "<CLIENT_ID>",
    "client_secret": "<CLIENT_SECRET>",
    "grant_type": "client_credentials"
  }'

Response:

{
  "tokenData": {
    "access_token": "<ACCESS_TOKEN>",
    "token_type": "Bearer",
    "expires_in": 31556926
  },
  "errors": [],
  "success": true
}

The access_token is the credential you present on subsequent calls. expires_in is the remaining lifetime in seconds (see Section 4 on what that value actually is in production). A successful response always carries success: true and an empty errors array.

The password and refresh-token grants use the same endpoint and the same response envelope; they differ only in the body (the password grant adds the user’s email and password; the refresh grant sends grant_type: refresh_token with a refresh_token value) and in that they additionally return a refresh_token alongside the access token.


4. Token lifecycle — documented vs. actual

This is worth understanding precisely, because it affects how much token-management code you need to write.

The OAuth2 response returns expires_in as a number of seconds. Vendor reference material shows a small illustrative value (on the order of a couple of hours) in its examples — which, taken at face value, would imply you need aggressive token-refresh logic.

In practice, that example does not reflect production behavior. Plenum has verified, empirically and across four live production environments, that the actual token lifetime is approximately 365 days (an expires_in on the order of 31556926 seconds). We surface this openly because it is the kind of detail that trips up teams who size their refresh logic to the documented example and then over-engineer around an expiry that won’t happen for a year.

Practical guidance:

  • For a machine-to-machine integration, mint a token, store it securely, and use it. You do not need a high-frequency refresh loop.
  • Treat a 401/access-denied response (Section 5) — not a timer — as your signal to re-mint. If you hold the client ID and secret, re-minting on the rare 401 is a single round-trip and can be fully automatic.
  • Do not hardcode an assumed expiry. Read expires_in from the response if you want to schedule renewal, rather than assuming either the documented example or the ~1-year figure.

5. Using the token, and handling errors

Once you hold a token, present it on every API request in the standard bearer header:

Authorization: Bearer <ACCESS_TOKEN>

(The Pl-Client header from Section 3 also accompanies each request.)

If you send a missing, malformed, or expired token, Grow responds with success: false and a structured error:

{
  "errors": [
    {
      "code": 400,
      "context": "access_denied",
      "message": "The resource owner or authorization server denied the request."
    }
  ],
  "success": false
}

Because success is an explicit boolean on every response, your integration can branch on it directly rather than inferring success from HTTP status alone. The recommended pattern is: on access_denied, re-mint a token and retry once; if it fails again, surface the error rather than looping.


6. Credentials: issuance, scoping, and rotation

Issuance. Plenum provisions a dedicated API client — a <CLIENT_ID> / <CLIENT_SECRET> pair — for your <SCHOOL_ID> environment during onboarding and delivers them to you over a secure channel. You do not need to self-provision anything inside Grow.

Scoping and isolation. Each environment has its own client and its own credentials. A credential issued for one organization’s environment cannot read another’s — isolation is structural, not a matter of policy. If your organization runs more than one Grow environment, each receives its own independently revocable credential set.

Secret handling. The <CLIENT_SECRET> is the sensitive value; treat it like any production secret (secret manager or environment variable, never source control, never an end-user device). The client ID is not itself secret but is conventionally stored alongside the secret.

Rotation. Rotating credentials — on a schedule, on personnel change, or in response to a suspected compromise — is a clean, no-downtime operation: Plenum revokes the old client and issues a new one, and you update the stored <CLIENT_SECRET>. No code changes are required, because nothing about the request shape changes. If you’d like to operate rotation yourself rather than through Plenum, we can arrange that during onboarding.


7. Where to go next

  • Real-Time Data: Webhooks — receiving signed, push-based events once you’re authenticated.
  • Pull-Based Data Access — the queryable record catalog and the event-log reconciliation pattern, all of which use the token described here.
  • Data Integration Overview — the high-level push/pull capability map, if you arrived here first.

Prepared by Plenum Solutions for your evaluation. “Grow by Plenum” is operated and supported by Plenum on your behalf. For integration design support, contact your Plenum engagement lead.