Generating an Authentication Token
Learn how to request a v2 OAuth-style access token for the Bitwave API and use it to authenticate subsequent API calls.
Overview
Bitwave's APIs require an OAuth-style access token for every request. New integrations should request tokens from the current v2 endpoint: POST /v2/oauth/token.
This guide walks through the v2 token request process, explains the request and response fields, and provides example requests. The legacy POST /oauth/token endpoint is deprecated and should only be used by existing integrations that have not yet migrated.
Endpoint Details
Use the following endpoint to obtain a new authentication token:
| Field | Value |
|---|---|
| Method | POST |
| URL | https://api.bitwave.io/v2/oauth/token (production). Substitute the appropriate base URL for sandbox or staging environments. |
| Content-Type | application/x-www-form-urlencoded |
| Query string | grant_type=client_credentials |
| Request Body | Form-encoded client_id and client_secret values. |
| Returns | A JSON object containing access_token, token_type, and expires_in. |
Legacy endpoint: POST /oauth/token is deprecated. It accepts grant_type in the form body, but new integrations should use POST /v2/oauth/token with grant_type in the query string.
Request Steps
Follow these steps to obtain a token with the v2 endpoint.
- Prepare your credentials.
- Add
grant_type=client_credentialsto the query string. - Construct the form-encoded request body.
- Send a POST request.
- Parse the response.
- Include the token in subsequent requests.
Request Parameters
| Parameter | Location | Required | Description |
|---|---|---|---|
| grant_type | Query string | Yes | OAuth grant type. For API-key authentication, use client_credentials. |
| client_id | Form body | Yes | Your Bitwave API key. |
| client_secret | Form body | Yes | Your Bitwave API secret. |
The v2 client-credentials flow does not honor a scope parameter. Do not include scope in new integrations.
Request Flow
- Prepare your credentials: Have your Bitwave API key and API secret, also called client ID and client secret, available. Store them securely, such as in environment variables.
- Add the grant type: Set
grant_type=client_credentialsin the URL query string. - Construct the request body: Create a form-encoded body containing
client_idandclient_secret. - Send a POST request: Submit the request to
/v2/oauth/token. Include theContent-Type: application/x-www-form-urlencodedheader. - Parse the response: If successful, the API returns an object with an
access_token. Extract this value for use in subsequent calls. Theexpires_inproperty is fixed at3600seconds. - Include the token: Set the HTTP header
Authorization: Bearer ACCESS_TOKENon every API request you make. When the token expires, call/v2/oauth/tokenagain with yourclient_idandclient_secretto obtain a fresh token.
Response Fields
The token endpoint returns a JSON object with these fields:
| Field | Description |
|---|---|
| access_token | The bearer token you must include in the Authorization header for subsequent requests. |
| token_type | Bearer, indicating that the token is used in a Bearer authorization header. |
| expires_in | 3600, the number of seconds until the token expires. Request a new token after this time. |
The token response does not include a refresh_token. To renew access, call /v2/oauth/token again with your client_id and client_secret.
Example Requests
Use the following examples to request an access token.
Example: cURL
curl -X POST "https://api.bitwave.io/v2/oauth/token?grant_type=client_credentials" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=YOUR_API_KEY&client_secret=YOUR_API_SECRET"Replace YOUR_API_KEY and YOUR_API_SECRET with your actual credentials.
Example: Node.js
The following Node.js example defines a small OAuth client to fetch a token and an Auth wrapper for convenience:
import axios from 'axios';
interface OauthResponse {
access_token: string;
token_type: 'Bearer';
expires_in: number;
}
class Oauth {
route = '/v2/oauth';
clientId: string;
clientSecret: string;
baseUrl: string;
constructor(baseUrl: string, clientId: string, clientSecret: string) {
this.baseUrl = baseUrl;
this.clientId = clientId;
this.clientSecret = clientSecret;
}
public async getToken(): Promise<OauthResponse> {
const url = `${this.baseUrl}${this.route}/token?grant_type=client_credentials`;
const body = new URLSearchParams({
client_id: this.clientId,
client_secret: this.clientSecret,
});
const resp = await axios.post<OauthResponse>(url, body);
return resp.data;
}
}
class Auth {
static async getToken(
baseUrl: string,
clientId: string,
clientSecret: string
): Promise<OauthResponse> {
const oauth = new Oauth(baseUrl, clientId, clientSecret);
return await oauth.getToken();
}
}
async function main() {
const apiKey = process.env.BITWAVE_API_KEY!;
const apiSecret = process.env.BITWAVE_API_SECRET!;
const authResp = await Auth.getToken('https://api.bitwave.io', apiKey, apiSecret);
console.log('Access token:', authResp.access_token);
}
main().catch((err) => console.error(err));After retrieving the token, include it in the Authorization header as Bearer ACCESS_TOKEN when calling Bitwave APIs.
Optional: Token Exchange Grant
The /v2/oauth/token endpoint also supports an OAuth 2.0 token-exchange grant for converting an existing Bitwave-issued JWT into another token form. This flow is primarily intended for browser and single-page-app integrations that already hold a valid Bitwave JWT and need to install it as a session cookie. For server-to-server access with an API key and secret, use the client-credentials flow above instead.
This endpoint requires authentication. Unlike client credentials, the token-exchange grant requires the caller to supply a valid existing Bitwave JWT on the request, in either of these forms:
Authorization: Bearer <jwt>header (preferred), or__sessioncookie carrying the JWT.
Requests with an invalid or expired token are rejected with 403.
Request
Pass the grant type and target token type as query-string parameters, and supply the source JWT in the Authorization header:
| Field | Location | Required | Description |
|---|---|---|---|
| grant_type | Query string | Yes | Must be urn:ietf:params:oauth:grant-type:token-exchange. |
| requested_token_type | Query string | Yes | Target token form. See the table below for accepted values. |
| Authorization header | Header | Yes | Bearer <jwt>. The existing Bitwave JWT to exchange. |
| requested_token_type | Effect |
|---|---|
urn:ietf:params:oauth:token-type:jwt | Validates the supplied JWT and returns it in the standard access_token response field. The token itself is unchanged; this form is useful for confirming a token is still valid and reading its remaining lifetime. |
urn:bitwave:params:oauth:token-type:cookie | Validates the supplied JWT and installs it as a __session cookie on the response. The access_token field in the response body is empty; the actual token is delivered via the Set-Cookie header. Used for browser/SPA flows. |
Response
For requested_token_type=urn:ietf:params:oauth:token-type:jwt:
{
"access_token": "<jwt>",
"token_type": "Bearer",
"expires_in": 3600
}For requested_token_type=urn:bitwave:params:oauth:token-type:cookie, the response body contains:
{
"access_token": "",
"token_type": "Cookie",
"expires_in": 3600
}and the response also includes a Set-Cookie: __session=... header. The cookie is HttpOnly, scoped to the API's parent domain (for example, .bitwave.io), uses SameSite=Lax, and is marked Secure when served over HTTPS. Subsequent same-site browser requests carry the cookie automatically.
Example: exchange a JWT for a session cookie
curl -X POST \
"https://api.bitwave.io/v2/oauth/token?grant_type=urn:ietf:params:oauth:grant-type:token-exchange&requested_token_type=urn:bitwave:params:oauth:token-type:cookie" \
-H "Authorization: Bearer EXISTING_JWT" \
-iThe -i flag prints response headers so you can see the Set-Cookie value the server returns. In a browser, the cookie is installed automatically and no further action is needed.
When to use which flow
grant_type=client_credentials— server-to-server access using a Bitwave API key and secret. This is the right choice for almost every backend integration.grant_type=urn:ietf:params:oauth:grant-type:token-exchange— browser/SPA integrations that already hold a Bitwave JWT (for example, after federated sign-in) and need to convert it to a session cookie or revalidate it.
Error Handling
When a request fails, the API returns a non-200 status code with an error object. Common failure scenarios include:
| Scenario | Response |
|---|---|
Missing or unsupported grant_type | 400 with JSON body { "code": "invalid_request", "message": "Invalid token request" }. Add grant_type=client_credentials to the query string. |
Missing client_id or client_secret | 400 with a plain-text body of Missing client_id or Missing client_secret. Include both values in the form-encoded request body. |
| Invalid credentials | 403 response with an empty body. Covers unknown client_id, disabled credentials, or incorrect client_secret. Verify your API key and secret. |
| Expired token on later API calls | Request a new token from /v2/oauth/token and retry the API call. |
Error responses may be a plain-text body (for missing parameters), an empty body (for 403 security errors), or a JSON object with code and message fields (for invalid requests). Inspect the HTTP status code together with the response body to determine the cause.
Best Practices
- Secure your secrets: Never hard-code your client credentials. Use environment variables or a secrets manager.
- Handle expiration: Tokens expire after
3600seconds. Request a fresh token before the current token expires. - Renew by re-authenticating: The endpoint does not return a refresh token. To renew access, call
/v2/oauth/tokenagain with yourclient_idandclient_secret. - Error logging: Log error responses for debugging and to help identify misconfigurations or network issues.
- Use v2 for new integrations: Treat
/oauth/tokenas a legacy endpoint and build new integrations against/v2/oauth/token. - Watch for rate-limit headers: Responses may include
X-RateLimit-Audit: would-blockandX-RateLimit-RetryAfterheaders when usage exceeds soft thresholds. These are informational today but are intended to become enforced limits — back off when you see them rather than waiting for hard429responses.
Next Steps
With a valid access token, you can authenticate calls to other Bitwave APIs, such as retrieving transactions or importing data. See the respective endpoint guides for details on constructing those requests.
