Skip to main content
The Connect API uses OAuth2 for authentication, allowing your application to access Penbox on behalf of users. This guide covers the complete authentication flow from initial authorization to token refresh.

Prerequisites

Before starting, you need:
  • Client ID: Your application identifier (e.g., my-app)
  • Client Secret: Secret key for authentication (provided by Penbox)
  • Redirect URI: HTTPS URL where users will be sent after authorization
Keep your client_secret secure! Never expose it in client-side code or public repositories.

OAuth2 Flow Overview

The authentication process follows the standard OAuth2 Authorization Code flow:

Step 1: Redirect to Authorization

Create a “Connect Penbox” button in your application that redirects users to:
https://connect.penbox.io/authorize?client_id={client_id}&redirect_uri={redirect_uri}&state={state}

Parameters

ParameterRequiredDescription
client_idYesYour client ID provided by Penbox
redirect_uriYesHTTPS URL where users will be redirected after authorization
stateNoRandom string to prevent CSRF attacks (recommended)
response_typeNoGrant type: code (default) or token
scopeNoRequested scopes, default: offline_access

Example Redirect

<a href="https://connect.penbox.io/authorize?client_id=my-app&redirect_uri=https://myapp.com/callback&state=random123">
  Connect Penbox Account
</a>
Or programmatically:
const authUrl = new URL('https://connect.penbox.io/authorize');
authUrl.searchParams.set('client_id', 'my-app');
authUrl.searchParams.set('redirect_uri', 'https://myapp.com/callback');
authUrl.searchParams.set('state', generateRandomState());

window.location.href = authUrl.toString();

Step 2: User Authorization

After redirection, the user will:
  1. Log in to Penbox (if not already logged in)
  2. Select a company (workspace) to authorize
  3. Approve your application’s access request
The authorization page shows:
  • Your application name
  • Which company they’re authorizing
  • What permissions you’re requesting

Step 3: Handle Redirect Callback

After authorization, the user is redirected back to your redirect_uri with query parameters.

Success Response

https://myapp.com/callback?code=ABC123XYZ&state=random123
ParameterDescription
codeAuthorization code to exchange for tokens (valid for 10 minutes)
stateThe state you provided (verify this matches!)

Error Response

https://myapp.com/callback?error=access_denied&error_description=User+denied+access&state=random123
ParameterDescription
errorError code (e.g., access_denied, unauthorized_client)
error_descriptionHuman-readable error description
stateThe state you provided
Always verify the state parameter matches what you sent to prevent CSRF attacks.

Step 4: Exchange Code for Tokens

Exchange the authorization code for an access token by making a POST request to /token.

Request

POST https://connect.penbox.io/token
Content-Type: application/json

{
  "client_id": "my-app",
  "client_secret": "your-secret-key",
  "grant_type": "authorization_code",
  "code": "ABC123XYZ",
  "redirect_uri": "https://myapp.com/callback",
  "custom_data": {
    "user_id": "12345",
    "account_email": "[email protected]"
  }
}

Parameters

ParameterRequiredDescription
client_idYesYour client ID
client_secretYesYour client secret
grant_typeYesMust be authorization_code
codeYesThe authorization code received
redirect_uriYesMust match the redirect_uri from step 1
custom_dataNoArbitrary data stored with the authorization

Response

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "B1M0YS2OBDg+MNnVXGholK1pJeCt4+SB6LVN71KqZ3NLAC0...",
  "id_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expiresIn": 86400
}
FieldDescription
access_tokenJWT token to use for API requests (valid for 24 hours)
refresh_tokenToken to get a new access_token when it expires
id_tokenJWT with user information (optional)
token_typeAlways Bearer
expiresInToken lifetime in seconds (86400 = 24 hours)
The refresh_token is only provided if you requested the offline_access scope (which is the default).

Step 5: Use Access Token

Include the access token in the Authorization header for all API requests:
GET https://connect.penbox.io/v1/requests
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Example: Create a Request

const response = await fetch('https://connect.penbox.io/v1/requests', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    flow: { slug: 'client-onboarding' },
    user: {
      email: '[email protected]',
      given_name: 'John',
      family_name: 'Doe'
    }
  })
});

Refreshing Access Tokens

Access tokens expire after 24 hours. Use the refresh token to get a new access token without requiring user re-authorization.

Request

POST https://connect.penbox.io/token
Content-Type: application/json

{
  "client_id": "my-app",
  "client_secret": "your-secret-key",
  "grant_type": "refresh_token",
  "refresh_token": "B1M0YS2OBDg+MNnVXGholK1pJeCt4+SB6LVN71KqZ3NLAC0..."
}

Parameters

ParameterRequiredDescription
client_idYesYour client ID
client_secretYesYour client secret
grant_typeYesMust be refresh_token
refresh_tokenYesThe refresh token received during authorization

Response

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expiresIn": 86400
}
Refresh tokens are long-lived but can be invalidated if:
  • User revokes authorization
  • Client credentials are regenerated
  • Security policy requires re-authentication

Custom Data

The custom_data field in the token exchange allows you to store arbitrary data with the authorization. This is useful for linking Penbox accounts to your system’s users.

Example Use Cases

Store User Reference
{
  "custom_data": {
    "user_id": "12345",
    "account_email": "[email protected]"
  }
}
Store Account Information
{
  "custom_data": {
    "company_id": "ABC-Corp",
    "subscription_tier": "premium",
    "integration_date": "2024-01-15"
  }
}
This data is stored in Penbox and can be retrieved when needed for reference or debugging.

Grant Types

The Connect API supports two OAuth2 grant types: Use for: Server-side applications where you can securely store the client_secret.
response_type=code
Provides both access_token and refresh_token for long-term access.

Implicit Grant

Use for: Client-side applications (SPAs) where client_secret cannot be stored securely.
response_type=token
Returns access_token directly in the redirect URL fragment. No refresh_token is provided.
The implicit grant is less secure and should only be used when absolutely necessary. Prefer authorization code flow for all server-side applications.

Token Security

Storing Tokens

Access Tokens
  • Store securely server-side (encrypted database, secure session)
  • Never expose in client-side code or URLs
  • Rotate regularly (every 24 hours via refresh)
Refresh Tokens
  • Treat as highly sensitive credentials
  • Encrypt at rest in your database
  • Never send to client-side code
  • Implement rotation on refresh

Revoking Access

Users can revoke your application’s access at any time through their Penbox account settings. When this happens:
  • All access tokens are immediately invalidated
  • Refresh tokens no longer work
  • API requests return 401 Unauthorized
  • You should prompt the user to re-authorize

Error Responses

Authorization Errors

Error CodeDescriptionAction
access_deniedUser denied authorizationInform user, allow retry
unauthorized_clientInvalid client_id or redirect_uriCheck configuration
invalid_requestMissing or invalid parametersFix request parameters
server_errorPenbox internal errorRetry later

Token Exchange Errors

Error CodeDescriptionAction
invalid_grantInvalid or expired authorization codeRequest new authorization
invalid_clientInvalid client credentialsCheck client_id and client_secret
unauthorized_clientClient not authorized for this grant typeContact Penbox support

Best Practices

Always include a random state parameter and verify it matches on the callback to prevent CSRF attacks.
Proactively refresh access tokens before they expire (e.g., after 23 hours) to avoid API disruptions.
Implement proper error handling for authorization failures, token expiration, and revocation.
Never expose tokens in client-side code, URLs, or logs. Encrypt refresh tokens at rest.
Only request the scopes your application actually needs.
Track token usage and expiration to detect issues early.

Next Steps