Skip to main content

Authentication & OAuth 2.0

Recommendation All integrations should use the OAuth 2.0 client credentials flow described below. New integrations should start here, and existing integrations using the legacy Service Account Key model are encouraged to migrate.


Overview​

ComboCurve is migrating from a per-tenant Service Account Key model to a standard OAuth 2.0 client credentials flow. The new model is simpler: instead of signing JWTs locally using a private key, your application exchanges a clientId and clientSecret for a short-lived bearer token issued by the ComboCurve token endpoint.

Legacy credentials (v1)OAuth credentials (v2)
Credentials fileapi-service-account-key.json (GCP SA private key)api-credentials.json (clientId + clientSecret)
Token generationYour app signs a JWT with RS256 using the private keyPOST to /v1/oauth/token — token is signed server-side
ComplexityRequires RS256 JWT signing libraryStandard HTTP POST
Token lifetimeConfigurable (typically 1 hour)1 hour
StatusLegacyCurrent

Note: "v1" and "v2" above refer to credential versions — the format of your downloaded ZIP and how you obtain tokens. They are unrelated to the /v1/ prefix in the API and token endpoint paths.


Obtaining Credentials​

Credentials are created and downloaded from the API & Sync page in your ComboCurve account:

  1. Navigate to https://yourcompany.combocurve.com/company/api-sync
  2. Click Create & Download
  3. The browser immediately begins downloading a ZIP archive

Choosing a credential version​

When you click Create & Download, what happens depends on the credential versions you already have active:

  • No credentials, or only v2 credentials: v2 is generated automatically — no dialog is shown.
  • At least one v1 credential exists: A dialog appears letting you choose between v1 and v2. v2 is pre-selected and recommended for all new credentials.

You can still generate v1 credentials by selecting v1 in the dialog. This is useful if you maintain integrations that use the legacy signing model and are not yet ready to migrate.

v2 ZIP contents​

combocurve/
api-credentials.json ← clientId, clientSecret, token endpoint URL
api-key.txt ← your API key (unchanged from v1)

api-credentials.json contents:

{
"type": "combocurve_oauth2_client",
"clientId": "<your-client-id>",
"clientSecret": "<your-client-secret>",
"token_uri": "https://api.combocurve.com/v1/oauth/token",
"audience": "https://api.combocurve.com"
}

v1 ZIP contents (legacy)​

combocurve/
api-service-account-key.json ← GCP service account private key (RS256 signing material)
api-key.txt ← your API key

One-time download — save it immediately​

Warning: Credentials can only be downloaded at the moment they are created. The download link expires after 1 hour and ComboCurve does not store your clientSecret (v2) or private key (v1) in a recoverable form. If you lose the ZIP file or miss the download window, you must revoke the credential and create a new one. There is no re-download option.

Save the ZIP to a secure location before closing the browser tab.

Credential limit​

Each company account supports a maximum of 3 active credentials at a time (across both v1 and v2). The Create & Download button is disabled once this limit is reached. To add a new credential you must first revoke an existing one from the API & Sync page.


Exchanging Credentials for a Token​

Once you have api-credentials.json, your application calls the token endpoint to receive a short-lived bearer token. You then use that token to authenticate all subsequent API requests. The token is valid for 1 hour — your application should proactively refresh it before it expires rather than waiting for a 401.

Endpoint​

POST /v1/oauth/token

Request​

Send a JSON body with the following fields:

FieldTypeRequiredDescription
grant_typestringYesMust be "client_credentials"
clientIdstringYesYour client ID from api-credentials.json
clientSecretstringYesYour client secret from api-credentials.json
curl -X POST https://api.combocurve.com/v1/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "client_credentials",
"clientId": "<your-client-id>",
"clientSecret": "<your-client-secret>"
}'

Response​

A successful request returns a 200 OK with a JSON body:

FieldTypeDescription
access_tokenstringSigned JWT bearer token
token_typestringAlways "Bearer"
expires_inintegerToken lifetime in seconds (3600 = 1 hour)
{
"access_token": "<signed-jwt>",
"token_type": "Bearer",
"expires_in": 3600
}

Error Responses​

StatusBodyCause
400{"error": "invalid_request"}Missing or malformed fields (e.g. wrong grant_type, invalid clientId format)
401{"error": "invalid credentials"}clientId not found or clientSecret does not match
429{"error": "too_many_requests"}Rate limit exceeded (see Rate Limits)

Using the Token​

Include the access token as a Bearer token in the Authorization header, alongside your API key and the x-backend-version: v2 header, on all API requests:

curl -X GET https://api.combocurve.com/v1/wells \
-H "Authorization: Bearer <access_token>" \
-H "x-api-key: <api-key>" \
-H "x-backend-version: v2"

Important — OAuth (v2) credentials require the x-backend-version: v2 header. OAuth (v2) credentials are served by ComboCurve's API Gateway backend, and the x-backend-version: v2 request header is what routes your request to it. If you omit this header, the request is sent to the legacy backend, which does not accept v2 credentials, and it will fail. Send x-backend-version: v2 on every request made with v2 credentials. (Legacy v1 credentials do not use this header.)

The Authorization and x-api-key headers are required on every request; omitting either returns a 401.


Rate Limits​

The token endpoint enforces two independent rate limits to prevent abuse:

ScopeLimitWindow
Per IP address100 requests15 minutes
Per clientId10 requests15 minutes

Both limits apply simultaneously. If either is exceeded the endpoint returns 429 Too Many Requests. The response includes RateLimit-* headers indicating when the window resets.

Since tokens are valid for 1 hour, a well-behaved client calls this endpoint at most once per hour — well within both limits. If your application is hitting 429 errors on the token endpoint, it is likely requesting a new token on every API call rather than caching and reusing it. See the code examples below for a caching pattern.


Code Examples​

The examples below read credentials from the files in the downloaded ZIP and cache the token in memory, refreshing it 60 seconds before it expires.

Python​

import requests
import json
import time

class ComboCurveAuth:
def __init__(self, credentials_path: str, api_key: str):
with open(credentials_path) as f:
creds = json.load(f)
self.token_uri = creds["token_uri"]
self.client_id = creds["clientId"]
self.client_secret = creds["clientSecret"]
self.api_key = api_key
self._token = None
self._expires_at = 0

def get_headers(self) -> dict:
if time.time() >= self._expires_at - 60:
self._refresh_token()
return {
"Authorization": f"Bearer {self._token}",
"x-api-key": self.api_key,
"x-backend-version": "v2", # required for OAuth (v2) credentials
"Content-Type": "application/json",
}

def _refresh_token(self):
response = requests.post(self.token_uri, json={
"grant_type": "client_credentials",
"clientId": self.client_id,
"clientSecret": self.client_secret,
})
response.raise_for_status()
data = response.json()
self._token = data["access_token"]
self._expires_at = time.time() + data["expires_in"]

# Load credentials from the downloaded ZIP
api_key = open("combocurve/api-key.txt").read().strip()
auth = ComboCurveAuth("combocurve/api-credentials.json", api_key)

# Token is fetched on first call and automatically refreshed when near expiry
response = requests.get("https://api.combocurve.com/v1/wells", headers=auth.get_headers())
print(response.json())

JavaScript / Node.js​

Requires Node.js 18+ (for the built-in fetch global).

const fs = require("fs");

class ComboCurveAuth {
constructor(credentialsPath, apiKey) {
const creds = JSON.parse(fs.readFileSync(credentialsPath, "utf8"));
this.tokenUri = creds.token_uri;
this.clientId = creds.clientId;
this.clientSecret = creds.clientSecret;
this.apiKey = apiKey;
this._token = null;
this._expiresAt = 0;
}

async getHeaders() {
if (Date.now() / 1000 >= this._expiresAt - 60) {
await this._refreshToken();
}
return {
Authorization: `Bearer ${this._token}`,
"x-api-key": this.apiKey,
"x-backend-version": "v2", // required for OAuth (v2) credentials
"Content-Type": "application/json",
};
}

async _refreshToken() {
const response = await fetch(this.tokenUri, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
grant_type: "client_credentials",
clientId: this.clientId,
clientSecret: this.clientSecret,
}),
});
if (!response.ok)
throw new Error(`Token request failed: ${response.status}`);
const data = await response.json();
this._token = data.access_token;
this._expiresAt = Math.floor(Date.now() / 1000) + data.expires_in;
}
}

async function main() {
// Load credentials from the downloaded ZIP
const apiKey = fs.readFileSync("combocurve/api-key.txt", "utf8").trim();
const auth = new ComboCurveAuth("combocurve/api-credentials.json", apiKey);

// Token is fetched on first call and automatically refreshed when near expiry
const headers = await auth.getHeaders();
const response = await fetch("https://api.combocurve.com/v1/wells", {
headers,
});
console.log(await response.json());
}

main();

Migrating from the Legacy Model (v1)​

If your current integration uses an api-service-account-key.json file and signs JWTs locally, follow these steps to migrate.

What changes​

  • The downloaded ZIP no longer contains api-service-account-key.json. Instead it contains api-credentials.json with a clientId and clientSecret.
  • You no longer need an RS256 JWT signing library. Token generation is a single HTTP POST.
  • The api-key.txt file and x-api-key header are unchanged.
  • The Authorization: Bearer <token> header is still required on every API request — only how you obtain the token changes.
  • OAuth (v2) requests must also include the x-backend-version: v2 header so they reach the backend that serves v2 credentials; without it the request fails. Legacy v1 credentials do not use this header.

Migration steps​

  1. Generate new v2 credentials — go to the API & Sync page and click Create & Download. If you already have at least one v1 credential, the version dialog will appear — select v2. Otherwise, v2 is created directly. Save the ZIP securely; you cannot re-download it later.

  2. Update your token logic — replace your RS256 JWT signing code with a call to POST /v1/oauth/token using clientId and clientSecret from api-credentials.json. Cache the returned token and refresh it ~60 seconds before expires_in elapses.

  3. Verify your integration — make a test API call with the new token. Confirm you receive a 200 response with correct data.

  4. Revoke the old v1 credential — once your integration is confirmed working, delete the old Service Account Key credential from the API & Sync page. v1 and v2 credentials work simultaneously, so you can run both during the switchover without any downtime.

Can I still generate v1 credentials?​

Yes, but only if you already have at least one v1 credential active. In that case, the Create & Download dialog appears and lets you select v1 or v2 — v2 is pre-selected but v1 remains available. If all your existing credentials are v2 (or you have none), the dialog is skipped and v2 is created directly. This is useful if you maintain multiple integrations and need to migrate them independently.

We recommend using v2 for any new credential you create.

What to do if you lose the credentials ZIP​

  1. Go to the API & Sync page and revoke the credential that was not saved.
  2. Click Create & Download to generate a new one and download it immediately.

Revoking a credential immediately invalidates any active tokens issued from it. If the credential was in active use by another system, update that system before revoking.

Your existing v1 credentials remain active​

Your current v1 credentials continue to work alongside any v2 credentials you create. There is no forced cutover — you migrate at your own pace. After migration, revoke the old v1 credential to free up a slot toward the 3-credential limit.