Session endpoint (Rust/Axum)
Implement the session endpoint on your backend with a server-side secret key, server-owned constraints and externalReference.
The session endpoint belongs to your backend. It must authenticate or rate-limit your own users, decide the file policy on the server, and bind the session to your business record with an opaque externalReference. The repository ships a runnable version of this example in examples/customer-backend.
Rust / Axum example
use axum::{extract::State, http::StatusCode, Json};
use serde_json::{json, Value};
#[derive(Clone)]
struct AppState {
http: reqwest::Client,
api_base: String, // e.g. https://api.crossupload.com
secret_key: String, // sk_… from your secret manager — never sent to browsers
site_origin: String, // https://www.example.com
}
/// POST /api/crossupload/session
async fn create_upload_session(
State(st): State<AppState>,
user: AuthenticatedUser, // your own auth extractor
Json(prefs): Json<Value>, // { maxFiles, locale } — preferences only
) -> Result<Json<Value>, StatusCode> {
// 1. Your own abuse protection.
if !user.rate_limit_ok("crossupload-session", 20) {
return Err(StatusCode::TOO_MANY_REQUESTS);
}
// 2. Server-owned constraints: clamp the browser's preference.
let max_files = prefs["maxFiles"].as_i64().unwrap_or(1).clamp(1, 3);
let application_id = user.current_application_id();
// 3. Bind to your record with an opaque reference.
let body = json!({
"externalReference": format!("user:{}:application:{}", user.id, application_id),
"maxFiles": max_files,
"maxFileSize": 10_000_000,
"allowedTypes": ["application/pdf", "image/jpeg", "image/png", "image/heic"],
"origin": st.site_origin,
"locale": if prefs["locale"] == "tr" { "tr" } else { "en" },
});
let resp = st.http
.post(format!("{}/v1/sessions", st.api_base))
.bearer_auth(&st.secret_key)
.json(&body)
.send().await.map_err(|_| StatusCode::BAD_GATEWAY)?;
if !resp.status().is_success() {
return Err(StatusCode::BAD_GATEWAY);
}
let session: Value = resp.json().await.map_err(|_| StatusCode::BAD_GATEWAY)?;
// 4. Remember which sessions you issued to this user.
user.remember_upload_session(session["id"].as_str().unwrap_or_default());
Ok(Json(session))
}
What the response contains
id,status,expiresAt— the session.desktopToken— a short-lived capability scoped to this one session (read, upload, manage, events). Safe to give to the visitor’s browser; it cannot touch other sessions or your account.pairing.url,pairing.qrSvg,pairing.expiresAt— the QR. The single-use secret is in the URL fragment.constraints— the effective, server-enforced limits.apiBase,branding,externalReference.
Rules that matter
- Client options are preferences. CrossUpload applies the strictest of plan, project and request limits, and you should clamp them on your side too.
- CORS and Origin allowlists are defense layers, not authentication. The secret key and session capabilities are the authorization.
- Idempotency: send
Idempotency-Keyto make retries safe. A replay returns the same session with fresh credentials; a different body with the same key returns409 idempotency_key_reused. - Don’t mint sessions for anonymous traffic without your own rate limits; every session counts against your plan’s initiation (abuse) limit, though not your accepted uploads.
Something unclear or wrong? Email [email protected].