Guides
Production integration checklist
The operational practices that make a Mahvash integration dependable: secrets, timeouts, retries, observability, background work, webhooks, SDK configuration and data handling — with a checklist to review before launch.
Before launch
Review this list before real users depend on your integration:
- The project API key lives only on your server, in a secret manager, with the smallest scopes it needs.
- Staging and production use different projects and different keys.
- Model choice comes from
GET /v1/modelsorauto, and your code handlesprovider_unavailable. - Every call has a connect timeout and an overall timeout.
- Output length (
max_output_tokens), file sizes and user input length are bounded. - Retries reuse the same
Idempotency-Key, and ambiguous outcomes are checked, not resent. - Logs record
X-Request-ID, status and latency — never prompts, files, keys or tokens. - You have exercised a success, a validation error, a rate limit and a stream disconnect against a non-production project.
- Project limits and a monthly budget are set, so a bug cannot run up unlimited cost.
Timeouts and retries
Set explicit timeouts. A reasoning model or a long document can take much longer than a normal HTTP call, so choose limits per operation instead of one global value, and prefer background mode for work that may take minutes.
const response = await fetch("https://api.mahvashai.ir/v1/responses", {
method: "POST",
headers: {
Authorization: "Bearer " + process.env.MAHVASH_API_KEY,
"Content-Type": "application/json",
"Idempotency-Key": job.idempotencyKey,
"X-Request-ID": job.traceId,
},
body: JSON.stringify({ model: job.model, input: job.input, max_output_tokens: 800 }),
signal: AbortSignal.timeout(90_000),
});
logger.info("mahvash call", {
status: response.status,
requestId: response.headers.get("X-Request-ID"), // log this, never the prompt or key
});- Honor
429andRetry-After. Back off exponentially with jitter for429and5xx. - A timeout after the request was sent is ambiguous. Retrieve the response or replay with the same idempotency key before sending new paid work.
- Cap concurrency on your side too. Mahvash refuses runs beyond
max_active_runs; queueing in your worker is cheaper than refused requests.
Observability
- Send your own trace ID as
X-Request-ID, or record the one Mahvash returns. Put it in every log line about the call and include it in support requests. - Store the response
id, model andusagenext to your own business record. This makes cost attribution and later investigation straightforward. - Track error rates by
error.typeanderror.code, latency percentiles, and the share ofincompleteresponses. - Watch organization and project usage so a budget alert is not your first signal of a problem.
Background work and webhooks
For background responses you can poll GET /v1/responses/{id}, or let Mahvash call you. Webhook subscriptions are created per project by an administrator with a user JWT:
curl -X POST https://api.mahvashai.ir/api/v1/chat/projects/<project-uuid>/webhooks \
-H "Authorization: Bearer $MAHVASH_USER_JWT" \
-H "X-Mahvash-Organization: acme" \
-H "Content-Type: application/json" \
-d '{"url": "https://hooks.example.com/mahvash", "event_types": ["response.completed", "response.failed"]}'
# The response contains the signing secret (whsec_...) exactly once.Events cover response outcomes: response.completed, response.failed, response.cancelled and response.incomplete. The payload contains identifiers, status, model, usage and an error code — never prompts or output — so fetch the full response with its ID when you need content.
Verifying deliveries
Each delivery carries webhook-id, webhook-timestamp and webhook-signature headers in the Standard Webhooks format used by OpenAI. Verify the signature over the exact raw body with your whsec_... secret before trusting anything in it:
import crypto from "node:crypto";
import express from "express";
const app = express();
const secret = Buffer.from(process.env.MAHVASH_WEBHOOK_SECRET.replace(/^whsec_/, ""), "base64");
// Verify the exact raw bytes; a re-serialized JSON body will not match.
app.post("/mahvash", express.raw({ type: "application/json" }), async (req, res) => {
const id = req.header("webhook-id");
const timestamp = req.header("webhook-timestamp");
const signatures = (req.header("webhook-signature") ?? "").split(" ");
if (!id || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return res.sendStatus(400);
const expected = "v1," + crypto
.createHmac("sha256", secret)
.update(id + "." + timestamp + ".")
.update(req.body)
.digest("base64");
const valid = signatures.some((candidate) =>
candidate.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(candidate), Buffer.from(expected)));
if (!valid) return res.sendStatus(400);
const event = JSON.parse(req.body.toString("utf8"));
if (await db.webhookEvents.exists(event.id)) return res.sendStatus(200); // duplicate
await db.webhookEvents.record(event.id);
await jobs.enqueue("sync-response", { responseId: event.data.id }); // work outside the request
res.sendStatus(200);
});- Reject deliveries whose timestamp is more than a few minutes old.
- Deduplicate on the event
id; the same event can arrive more than once. - Tolerate out-of-order delivery, and make the handler idempotent.
- Respond quickly with
2xxand do the real work in a queue. A receiver that keeps failing is disabled automatically. - Rotate the secret with
POST /api/v1/chat/webhooks/{id}/rotate; the old secret keeps signing for 24 hours so you can redeploy safely.
Using the OpenAI SDKs
For supported /v1 operations, the official OpenAI SDKs work with Mahvash when you set baseURL (JavaScript) or base_url (Python) to https://api.mahvashai.ir/v1 and pass a Mahvash project key. Keep these points in mind:
- Mahvash-only request fields, such as
asset_idsormanaged_tools, are sent through the SDK's extra-body option or with direct HTTP. - Workspace endpoints under
/api/v1are not part of the OpenAI SDKs. Call them with plain HTTP and read the product envelope. - SDKs retry some failures automatically. Configure retries deliberately so they respect your idempotency strategy.
- Pin your SDK version and test the quickstart, a stream, and an error path whenever you upgrade.
Data handling
- Send only the data the task needs. Remove secrets, tokens and unnecessary personal data from prompts and files.
- Use
store: falsefor requests whose content should not be kept after delivery; retrieval and idempotent replay are then unavailable for that response. - Delete responses, files and vector stores you no longer need, according to your retention policy.
- Keep your own authorization in front of everything you show users: Mahvash scopes data to projects, but only your application knows which of your users may see which document or answer.