Code examples
The same end-to-end flow (Make your first call) in three languages. Each example reads credentials from environment variables — set them before running:
export E1_CLIENT_ID="<your_client_id>"export E1_CLIENT_SECRET="<your_client_secret>"export E1_COMPANY_ID="<your_company_id>"Each example caches the bearer token in memory and refreshes it before expiry. Don’t persist tokens to disk — see Security — Tokens.
curl
A two-step recipe: fetch a token, capture it, call the API. Add jq to extract the access token cleanly.
TOKEN=$(curl -s -X POST https://auth.api.estimateone.com/oauth2/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials" \ -d "client_id=$E1_CLIENT_ID" \ -d "client_secret=$E1_CLIENT_SECRET" \ -d "scope=speci-finder/read" | jq -r .access_token)
curl -s \ "https://api.estimateone.com/api/supplier/v1/companies/$E1_COMPANY_ID/speci-finder/results" \ -H "Authorization: Bearer $TOKEN" \ -H "x-e1-client-id: $E1_CLIENT_ID" \ | jq '.data[] | { projectId, projectName, tenders: [.tenders[].name] }'Node.js (fetch)
Built-in fetch (Node 18+). No external dependencies needed.
const TOKEN_URL = 'https://auth.api.estimateone.com/oauth2/token';const API_BASE = 'https://api.estimateone.com';
const clientId = process.env.E1_CLIENT_ID;const clientSecret = process.env.E1_CLIENT_SECRET;const companyId = process.env.E1_COMPANY_ID;
let cached = { token: null, expiresAt: 0 };
async function getToken() { // Refresh ~60s before expiry if (cached.token && Date.now() < cached.expiresAt - 60_000) { return cached.token; } const body = new URLSearchParams({ grant_type: 'client_credentials', client_id: clientId, client_secret: clientSecret, scope: 'speci-finder/read', }); const res = await fetch(TOKEN_URL, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body, }); if (!res.ok) throw new Error(`token fetch failed: ${res.status}`); const json = await res.json(); cached = { token: json.access_token, expiresAt: Date.now() + json.expires_in * 1000, }; return cached.token;}
async function getSpeciFinderResults() { const token = await getToken(); const url = new URL( `/api/supplier/v1/companies/${companyId}/speci-finder/results`, API_BASE, );
const res = await fetch(url, { headers: { Authorization: `Bearer ${token}`, 'x-e1-client-id': clientId, }, });
if (res.status === 401) { cached = { token: null, expiresAt: 0 }; throw new Error('unauthorised — token discarded, retry once'); } if (!res.ok) throw new Error(`api call failed: ${res.status}`); return res.json();}
const { data } = await getSpeciFinderResults();console.log(`Got ${data.length} matched projects`);Python (requests)
import osimport timeimport requests
TOKEN_URL = "https://auth.api.estimateone.com/oauth2/token"API_BASE = "https://api.estimateone.com"
client_id = os.environ["E1_CLIENT_ID"]client_secret = os.environ["E1_CLIENT_SECRET"]company_id = os.environ["E1_COMPANY_ID"]
_cache = {"token": None, "expires_at": 0.0}
def get_token() -> str: # Refresh ~60s before expiry if _cache["token"] and time.time() < _cache["expires_at"] - 60: return _cache["token"]
res = requests.post( TOKEN_URL, data={ "grant_type": "client_credentials", "client_id": client_id, "client_secret": client_secret, "scope": "speci-finder/read", }, timeout=10, ) res.raise_for_status() payload = res.json() _cache["token"] = payload["access_token"] _cache["expires_at"] = time.time() + payload["expires_in"] return _cache["token"]
def get_speci_finder_results() -> dict: token = get_token() res = requests.get( f"{API_BASE}/api/supplier/v1/companies/{company_id}/speci-finder/results", headers={ "Authorization": f"Bearer {token}", "x-e1-client-id": client_id, }, timeout=15, ) if res.status_code == 401: _cache["token"] = None # force refresh on next call res.raise_for_status() return res.json()
if __name__ == "__main__": payload = get_speci_finder_results() print(f"Got {len(payload['data'])} matched projects")Generating an SDK
If your stack benefits from a typed client, generate one from the OpenAPI 3.1 spec using your tool of choice (openapi-generator, openapi-typescript, oapi-codegen, etc.). Regenerate periodically to pick up non-breaking additions — see Versioning.