One file, no dependencies. Works in Node 18+ and modern browsers (built-in fetch, FormData, Web Crypto). Version 1.7.2 (the SDK_VERSION constant in the file). The client has a method for every API operation.
Install
Download grem.ts and put it in your project. There is no npm package.
Quickstart
import { GremClient } from './grem';
const grem = new GremClient({ apiKey: process.env.GREM_API_KEY });
const { estimate } = await grem.valuation.express({
propertyCategory: 'apartment',
addressText: 'Torstraße 12, Berlin',
totalArea: 64,
});
console.log(estimate.pricePoint, estimate.currency);
The default base address is https://developers.grem.capital/api/v1 (option baseUrl). Create a key in the cabinet: API → Keys.
Jobs
Every job response carries jobId (API 1.6.0+). jobs.wait polls the status (free) until state is completed, partial, failed or action_required. A failed poll (network, 5xx, 429) does not stop it: it keeps polling, up to 5 failures in a row:
const job = await grem.text.generate({ city: 'Berlin', totalArea: 64, language: 'en' });
const done = await grem.jobs.wait('text/generate', job.jobId);
if (done.state === 'failed') throw new Error('generation failed');
A report review waits for confirmation:
const form = new FormData();
form.append('file', pdfBlob, 'report.pdf');
const review = await grem.valuation.review(form);
await grem.valuation.confirmReview(review.jobId);
const result = await grem.jobs.wait('valuation/review', review.jobId);
What the client has
| Group | Methods |
|---|---|
| key | ping(), me() |
| valuation | valuation.express, valuation.report, valuation.rent, valuation.review, valuation.confirmReview |
| content | content.generate, text.generate, video.create, video.music, video.endings, avatar.video, avatar.list |
| materials | site.create, pdf.create, flyer.create, draft.extract, photo.enhance, floorplan.create |
| reference | voices(), toolsStatus(), works(), dictionaries() |
| jobs and files | jobs.get, jobs.wait, artifact(path, { range, signal }), uploads.images |
| listings | objects.list, get, create, update, delete, setAgency, setAssignee |
| leads | leads.list, get, create, update, delete, matches, claim, returnToPool |
| Swipe Match | match.list, get, events, sendEvent |
| webhooks | webhooks.list, create, update, delete, test, deliveries, resend |
| chat | chat.createSession, listSessions, updateSession, deleteSession, sendMessage, history, stream |
Tools with files take FormData - how to fill it is described on each tool's page.
Sandbox
With a test key the simulate option picks the outcome of every simulated call (Sandbox):
const grem = new GremClient({ apiKey: process.env.GREM_TEST_KEY, simulate: 'failed' });
With a live key the simulate option throws when the client is created: the call would run for real.
Requests wait up to 5 minutes by default (timeoutMs): starting a tool can take over a minute, and giving up early stops neither the work nor its charge.
The client repeats a refused call after Retry-After, up to maxRetries times (default 3, at most 10; 0 turns retries off): 429 always - a rate-limited call was not processed; 503 for reads, and for writes only when you pass an Idempotency-Key - a 503 can follow a side effect. A Retry-After over 30 seconds is not waited for: the error is thrown at once. Your AbortSignal also cancels the pause. Pass that signal to request(). Without Retry-After the pauses are 1, 2, 4 ... seconds. An upload (uploads.images) is not repeated after 503 (after 429 it is: the upload was not processed). A signal you pass replaces the request timeout (timeoutMs).
Errors
A 4xx or 5xx answer throws a GremError with status, title, detail, code, errors, requestId and retryAfter (for 429 and 503):
import { GremError } from './grem';
try {
await grem.text.generate({});
} catch (e) {
if (e instanceof GremError) console.error(e.status, e.title, e.code, e.detail);
}
Safe retries
The last argument of methods that start a tool or create a record is the Idempotency-Key:
await grem.pdf.create({ pdfName: 'Presentation', objectId, language: 'ENG' }, crypto.randomUUID());
uploads.images takes a key too, but the API ignores it: a repeated upload stores the files again.
Webhooks
import { verifyWebhookSignature } from './grem';
app.post('/grem-hook', express.raw({ type: 'application/json' }), async (req, res) => {
const raw = req.body.toString('utf8');
const ok = await verifyWebhookSignature(process.env.GREM_WEBHOOK_SECRET, req.get('x-grem-signature'), raw);
if (!ok) return res.status(400).send('invalid signature');
const event = JSON.parse(raw);
res.sendStatus(200);
});
Chat
const session = await grem.chat.createSession();
const stop = new AbortController();
const streaming = grem.chat
.stream(session.sessionId, (data) => console.log(data), stop.signal)
.catch((e) => { if (e.name !== 'AbortError') throw e; }); // stop.abort() rejects with AbortError
await grem.chat.sendMessage(session.sessionId, 'Hello!');
stop.abort(); // when you no longer need the stream
await streaming;
Without the third argument sendMessage sends a random clientMessageId (Node 18 included). To retry a message safely, pass your own id and reuse it: the same id is answered and charged once.