Session secrets
Every session has its own secret, returned once when it starts. Later calls on the session or its documents send it, so a leaked key alone reads nothing.
What a session secret is
POST /v1/sessions returns a turn with a top-level session_secret (dss_…). It is returned only there and never shown again; we store only a hash of it. A session opens only for your organisation's key and its secret.
Your key lives in your server's environment and the secrets in your database, so someone who gets hold of the key alone can't read your users' conversations or documents.
Store it
Save the session ID, its secret and your own user's ID together, as soon as the session starts. That row is how you find a user's sessions later and how you check that a request is theirs.
// Your server, behind your own login.app.post("/api/sessions", requireLogin, async (req, res) => { const r = await fetch(`${DOCGEN}/v1/sessions`, { method: "POST", headers: { Authorization: `Bearer ${process.env.DOCGEN_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ message: req.body.message }), }); const turn = await r.json(); if (!r.ok) return res.status(r.status).json(turn); // Store the session ID, its secret and your user's ID together. await db.sessions.insert({ id: turn.session.id, secret: turn.session_secret, user_id: req.user.id, }); const { session_secret, ...rest } = turn; // never send the secret to the browser res.status(201).json(rest);});If the response is lost, retry with the same Idempotency-Key: for 24 hours the replay returns the same session and the same secret. A secret you lose after that can't be recovered; start a new session.
Send it
Send Docgen-Session-Secret on every call after the session starts: reading, messaging, answers, closing and deleting it, and POST /v1/documents with its session. A document takes the secret of the session it was made from, on GET /v1/documents/{document} and its stream.
curl https://api.129.212.147.181.sslip.io/v1/sessions/$SESSION_ID \ -H "Authorization: Bearer $DOCGEN_API_KEY" \ -H "Docgen-Session-Secret: $SESSION_SECRET"The signed download links in files.*.url need neither the key nor the secret: the link is the credential, and it expires.
Your users
There is no call that lists sessions. Your database already knows which sessions belong to which user, so a “your documents” page reads from there.
app.post("/api/sessions/:id/messages", requireLogin, async (req, res) => { // Only this user's sessions: the lookup is your permission check. const s = await db.sessions.findOne({ id: req.params.id, user_id: req.user.id }); if (!s) return res.sendStatus(404); const r = await fetch(`${DOCGEN}/v1/sessions/${s.id}/messages`, { method: "POST", headers: { Authorization: `Bearer ${process.env.DOCGEN_API_KEY}`, "Docgen-Session-Secret": s.secret, "Content-Type": "application/json", }, body: JSON.stringify({ text: req.body.text }), }); res.status(r.status).json(await r.json());});When it fails
- No header: 401 session_secret_missing. It is checked before anything is looked up, so it says nothing about whether the session exists.
- A wrong secret: 404 session_not_found or 404 document_not_found, exactly as if the ID didn't exist.
Server only
Keep the secret on your server, like the key. Secret keys are refused from browsers: a request with an Origin header gets 401 secret_key_in_browser. Your page calls your server; your server adds the key and the session's secret and calls us. The same goes for streams.
Docgen-Subject
Docgen-Subject is an optional label for your end user (a user ID or a hash of one, never an email) sent when a session starts. We store it and echo it in session.subject. It grants no access: that rests on the secret.