SurveyJS Server Integration · PHP / Laravel IV.4 · Turn paper, PDF and images into responses
You work in a private copy of the demo database, kept until it has been unused for 24 hours.

IV.4

Turn paper, PDF and images into responses

You need this: When forms still arrive on paper, as scans or as filled PDFs.

The AI Form Response Extractor (MIT) takes the definition and a file, and returns answers in the form's own shape, ready to open in the form for a person to review. It calls the AI provider you configure with your own key; nothing is sent to SurveyJS. Here the SurveyJS service runs it next to this app; the route sends the scan as base64 JSON and saves nothing.

This step on the Server Integration page Source on GitHub

The SurveyJS service is called at http://surveyjs:3000. If it isn't running, the endpoint answers 503 "SurveyJS service is not running": start it with docker compose up -d surveyjs.

Try this

  1. (work-order-scan.png) or : one POST /api/work-orders/extract fills the form below as a draft.
  2. Answers read with low confidence are marked "Check this answer"; the unique id (from the QR code) is shown when found.
  3. "What the server stored" shows the same row counts before and after: extraction saves nothing.

No scan yet.

Code that just ran

These regions are read from the files that served this page, the same lines the Server Integration page shows.

Client · shared/client/extract-from-paper.js · sjs:IV.4.client View on GitHub
// Upload the scan; open the draft in the form for review
async function extract(file) {
  const body = new FormData();
  body.append("scan", file);
  const res = await fetch("/api/work-orders/extract", { method: "POST", body });
  const result = await res.json();
  if (!res.ok) throw new Error(result.error);
  survey.data = result.answers;   // a person checks and corrects, then saves as usual
  return result;                  // { answers, confidence, uniqueId }
}
Server · routes/examples/extract-from-paper.php · sjs:IV.4.server View on GitHub
// POST /api/work-orders/extract — send the uploaded scan to the SurveyJS service; return a draft response
Route::post('/api/work-orders/extract', function (Request $request) {
    $scan = $request->file('scan') ?? abort(400, 'Upload the scan in a field named "scan"');
    abort_if(! $scan->isValid() || $scan->getSize() > 5 * 1024 * 1024, 413, 'The scan must be a file of 5 MB or less');
    try {
        $out = Http::baseUrl(config('surveyjs.service_url'))->timeout(120)->post('/extract', [   // JSON with base64, not multipart
            'schema' => json_decode(DB::table('forms')->where('key', 'work-order')->value('json')), 'document' => base64_encode($scan->get())]);
    } catch (ConnectionException $e) {                                         // cURL error 28 is a timeout
        return str_contains($e->getMessage(), 'cURL error 28')
            ? response()->json(['error' => 'SurveyJS service timed out'], 504)
            : response()->json(['error' => 'SurveyJS service is not running at '.config('surveyjs.service_url')], 503);
    }
    $result = $out->object();

    return match (true) {                                                       // nothing is saved: a person reviews the draft
        $out->status() === 200 && is_object($result) && is_object($result->data ?? null) => response()->json(
            ['answers' => $result->data, 'confidence' => $result->confidence ?? [], 'uniqueId' => $result->uniqueId ?? null]),
        $out->status() === 400 && $out->json('error') === 'INVALID_DOCUMENT' => response()->json(['error' => 'The file is not a PDF, PNG, JPEG, WebP or GIF document'], 400),
        $out->status() === 503 && $out->json('error') === 'AI_NOT_CONFIGURED' => response()->json(['error' => 'The SurveyJS service has no AI provider configured'], 503),
        $out->status() === 502 && $out->json('error') === 'EXTRACTION_FAILED' => response()->json(['error' => 'Could not extract the response from the document'], 502),
        default => tap(response()->json(['error' => 'SurveyJS service error'], 502), fn () => Log::error('SurveyJS service: unexpected answer from /extract', ['status' => $out->status(), 'body' => substr($out->body(), 0, 2000)])),
    };
});

Definition: shared/definitions/extract-from-paper.json