Create a deposit
Create a deposit on behalf of a linked rental operator, send the tenant to Gando to secure it, and track the outcome. This recipe covers the full partner integration path from API call to an active deposit.
Time: ~15 minutes
API: POST /api/partner/v1/deposits
Integration path — recommended order for a new partner integration:
1. Connect → 2. Webhooks → 3. Deposits (this recipe)
The
@gando/partnernpm package is forthcoming (Speakeasy). Examples below reflect the intended SDK API.
Prerequisites
-
Install the SDK
npm install @gando/partner -
Partner API key — issued by Gando, prefix
gando_pk_test_…for staging. -
Staging environment — point requests at
https://stagingv2.gando.app(production:https://gando.app). -
Linked rental operator — you need a
GANDO_ACCOUNT_IDfor an account actively linked to your partner. Complete Recipe 01 — Link a rental operator first if none exist. List linked accounts:
import { Gando } from '@gando/partner';
const gando = new Gando({
apiKey: process.env.GANDO_API_KEY!,
serverURL: process.env.GANDO_BASE_URL ?? 'https://stagingv2.gando.app',
});
// Print each linked rental operator account id
for (const acct of (await gando.accounts.list()).object?.data ?? []) {
console.log(acct.accountId);
}
- Runnable examples repo (for the full script at the end): gando-partner-js-examples (forthcoming).
- Webhooks configured (recommended) — Recipe 02 — Receive webhooks so you track deposit status via
deposit.activatedinstead of polling.
Flow
Step 1 (optional) — Initialize the client
If you have not already, instantiate the API client once and reuse it:
import { Gando } from '@gando/partner';
const gando = new Gando({
apiKey: process.env.GANDO_API_KEY!,
serverURL: 'https://stagingv2.gando.app',
});
The SDK sends your key as x-api-key on every request.
Step 2 — Create the deposit
Build the request body. Amounts are in euros (not cents). 800.0 = €800.00.
const response = await gando.deposits.create({
// Rental operator account — must be linked to your partner account (403 otherwise)
accountId,
// Deposit amount in EUR. Min €70. Default max €2,500 per operator (negotiated ceiling)
amount: 800.0,
// Your booking / contract reference for reconciliation in your PMS
rentalContract: 'CTR-2026-042',
// Rental period start (ISO 8601 UTC)
contractStartAt: '2026-04-01T00:00:00.000Z',
// Rental period end — must be on or after contractStartAt
contractEndAt: '2026-04-10T23:59:59.000Z',
// Optional: client id from POST /api/partner/v1/clients on the same account
clientId: undefined,
// When true, response includes depositUrl for immediate tenant redirect
depositUrlGeneration: true,
// HTTPS URL Gando redirects to after checkout (required with depositUrlGeneration)
returnUrl: 'https://partner.example/checkout/complete',
});
const deposit = response.object?.data;
Response (201):
| Field | Description |
|---|---|
id | Deposit id (dep_…) — store this on your booking |
reference | Human-readable reference (GAN-…) |
status | Starts as pending |
depositUrl | Tenant checkout URL (only when depositUrlGeneration: true) |
Tip: Pass an optional Idempotency-Key header (UUID v4) on create to safely retry network failures without duplicate deposits. Same key + same body replays the cached response for 24 hours.
Step 3 — Send the link to the customer
Two delivery options. Pick one per booking flow.
Option A — You deliver the link
Redirect the tenant in your app, or send the link yourself (email, SMS, QR):
// depositUrl is only present when depositUrlGeneration was true
if (deposit?.depositUrl) {
res.writeHead(302, { Location: deposit.depositUrl });
res.end();
}
Option B — Gando sends the email
If you already have the tenant's email and prefer Gando to deliver the link:
await gando.deposits.sendDepositMail({
id: deposit!.id!,
email: 'tenant@example.com',
});
For multiple recipients, use gando.deposits.sendEmails() with a recipients list.
Step 3 bis — Inline redirect in your booking funnel
For combined booking + deposit checkout, create the deposit before the final confirmation step and redirect immediately:
-
Set
depositUrlGeneration: trueand areturnUrlon your booking completion page. -
After create, redirect the browser to
depositUrl. -
When the tenant finishes on Gando, they land on
returnUrlwith query params:Param Values depositIddep_…depositStatussecured,declined, orabandonedExample return URL:
https://partner.example/checkout/complete?depositId=dep_abc123&depositStatus=secured -
Do not trust the query params alone — they reflect the tenant-facing outcome. Always confirm the authoritative API status via polling or webhooks (Step 4).
Handle the return URL in your route handler:
const url = new URL(req.url!, `http://${req.headers.host}`);
const depositId = url.searchParams.get('depositId');
const outcome = url.searchParams.get('depositStatus');
switch (outcome) {
case 'secured':
// confirm booking, show success
break;
case 'declined':
// release hold, notify ops
break;
case 'abandoned':
// release hold, offer retry
break;
default:
// verify with GET deposit
break;
}
Step 4 — Track the deposit status
The deposit moves through statuses as the tenant completes checkout. You need a reliable way to know when it reaches active.
Option A — Polling (deposits.retrieve)
Suitable for prototypes or low volume. Poll until a terminal or active state is reached.
import type { Gando } from '@gando/partner';
const TERMINAL_STATUSES = ['active', 'cancelled', 'payment_issue', 'close'] as const;
function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function waitForDeposit(
gando: Gando,
depositId: string,
maxAttempts = 30,
): Promise<{ status: string } | null> {
const intervals = [2_000, 3_000, 5_000, 5_000, 10_000]; // milliseconds between attempts
for (let i = 0; i < maxAttempts; i++) {
const deposit = (await gando.deposits.retrieve({ id: depositId })).object?.data;
const status = deposit?.status;
if (status && (TERMINAL_STATUSES as readonly string[]).includes(status)) {
return { status };
}
await sleep(intervals[Math.min(i, intervals.length - 1)]!);
}
return null; // timeout — check again later or rely on webhooks
}
Recommended intervals:
| Phase | Interval | Rationale |
|---|---|---|
| First 30 s | every 2–3 s | Tenant is likely still on Gando checkout |
| 30 s – 2 min | every 5 s | Scoring + 3DS can take time |
| After 2 min | every 10–30 s | Back off; switch to webhooks for production |
Stop polling once status is active, cancelled, payment_issue, or close.
Option B — Webhooks (recommended)
Register an HTTPS endpoint and subscribe to deposit events. Gando pushes status changes in real time — no polling loops, no missed transitions.
Subscribe to at minimum:
deposit.status_changed— wildcard for every transitiondeposit.activated— deposit becameactive
See Recipe 02 — Receive webhooks for endpoint setup, HMAC verification, and event handling.
Step 5 — Handle the active deposit
When status is active, the deposit is secured. The tenant's funds are not blocked; Gando holds a collection guarantee for the rental operator.
What to do:
- Mark the booking as deposit-secured in your PMS — link
deposit.idto your booking record. - Store
reference(GAN-…) for support and reconciliation. - Respect the securing window — coverage lasts until
expiresAton the deposit (derived fromcontractEndAt, default max 60 days from activation). After expiry the deposit moves toclose. - On damage during the rental — the rental operator triggers a capture (encaissement) via dashboard or
POST /api/partner/v1/deposits/{id}/capture. That is outside this recipe.
Status reference:
| Status | Meaning |
|---|---|
pending | Created, awaiting tenant checkout |
incomplete | Tenant started but did not finish |
active | Secured — deposit covers the lease |
cancelled | Voided before or during checkout |
payment_issue | Fee payment failed |
close | Natural end of contract / expiry |
captured | Amount collected on claim |
Common errors
All errors return the same envelope: { "error": { "code", "message", "requestId" } }.
400 — Bad request
| Cause | Fix |
|---|---|
Missing or invalid field (accountId, amount, dates) | Validate body against the schema; all required fields must be present |
amount below €70 or above operator max | Use 70–2500 EUR (default ceiling); check error message for resolved limit |
contractEndAt before contractStartAt | Ensure end ≥ start |
returnUrl uses http:// (non-localhost) | Use HTTPS, or http://localhost for local dev |
Invalid clientId | Create the client first via POST /api/partner/v1/clients on the same accountId |
401 — Authentication failed
error.code | Cause | Fix |
|---|---|---|
missing_api_key | No x-api-key or Authorization header | Pass gando_pk_… in every request |
invalid_api_key | Wrong or unknown key | Copy the key from Gando dashboard; use test key on staging |
api_key_revoked | Key was revoked | Generate a new partner API key |
403 — Forbidden
error.code | Cause | Fix |
|---|---|---|
account_not_linked | accountId is not linked to your partner | Complete partner connect for that rental operator, or pick a linked account from accounts.list() |
account_revoked | Link was revoked | Re-link the rental operator via connect |
deposit_access_denied | Deposit belongs to another partner | Use the correct accountId and partner key |
Log requestId from every error when contacting Gando support.
Full code
Runnable end-to-end script — see gando-partner-js-examples (forthcoming).
git clone https://github.com/Gando-Solutions/gando-partner-js-examples.git
cd gando-partner-js-examples
npm install
cp .env.example .env
# Set GANDO_API_KEY, GANDO_ACCOUNT_ID
npx tsx examples/03-create-deposit.ts
/**
* Create a deposit for a linked rental operator (Partner API).
*
* Usage: npx tsx examples/03-create-deposit.ts
*/
import { Gando } from '@gando/partner';
const accountId = process.env.GANDO_ACCOUNT_ID;
if (!accountId) {
console.error('Set GANDO_ACCOUNT_ID');
process.exit(1);
}
const gando = new Gando({
apiKey: process.env.GANDO_API_KEY!,
serverURL: process.env.GANDO_BASE_URL ?? 'https://stagingv2.gando.app',
});
const year = new Date().getFullYear();
const contractStartAt = new Date().toISOString();
const contractEndAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString();
try {
const response = await gando.deposits.create({
accountId,
amount: 800.0,
rentalContract: `CTR-${year}-${Math.floor(Math.random() * 900) + 100}`,
contractStartAt,
contractEndAt,
clientId: undefined,
depositUrlGeneration: true,
returnUrl: 'https://partner.example/checkout/complete',
});
const deposit = response.object?.data;
console.log('Deposit created');
console.log(` id: ${deposit?.id}`);
console.log(` reference: ${deposit?.reference}`);
console.log(` status: ${deposit?.status}`);
if (deposit?.depositUrl) {
console.log(` deposit_url: ${deposit.depositUrl}`);
}
} catch (err) {
console.error('API error:', err);
process.exit(1);
}
Next steps
You have completed the core integration path (Connect → Webhooks → Deposits).
Optional enhancements:
- Create a client via
POST /api/partner/v1/clients— pre-fill tenant info before creating the deposit - Partner API reference — capture, cancel, list deposits, and more
Earlier in the path (if you skipped a step):
- Recipe 01 — Link a rental operator — Partner Connect signed URLs
- Recipe 02 — Receive webhooks — production-grade event delivery