Skip to main content
A plugin can send files (a fuel or handling receipt, a loadsheet) inside the PIREP submission itself, by returning them with attachFile() from its ctx.flight.attachToPirepSubmission handler. This page is for whoever maintains the airline’s PIREP-receiving backend, not the plugin author.

What changes

Only one thing changes the PIREP submission: a plugin returning attachFile() from its attachToPirepSubmission handler. When that happens for a flight, that flight’s submission is sent as multipart/form-data instead of application/json. Everything else leaves the submission exactly as it has always been, byte-identical JSON. That includes plugins that handle files some other way. A screenshot plugin that uploads each screenshot to its own endpoint as it is taken, for example, never touches the PIREP submission, so your completion endpoint receives the same JSON it always has.
If none of your installed plugins return attachFile() from a submission handler, nothing on this page applies to you.
The switch is decided per submission, not per airline or per plugin. A plugin that only sometimes has a file to attach (no receipts on this leg, say) produces a plain JSON submission on the legs where it returns none.

Wire format

The PIREP travels as a single form field named payload, holding exactly the JSON body your backend already receives when no file is attached. Each attached file travels as its own part, named <pluginId>_<n>, numbered from 0 per plugin. Inside the JSON, every attachment is replaced with { "$file": "<part name>" } pointing at its part. So a submission that would otherwise be:
arrives as a multipart request with three parts: payload carrying that JSON verbatim as a string, plus ground-ops_0 and ground-ops_1 carrying the files, each with its own filename and content type. Recovering it is one step:
Because the PIREP is one field rather than one field per value, types survive exactly as they do on the JSON path: booleans stay booleans, null stays null, and an empty array stays an empty array. Nothing about the body changes when a file is attached except that it is wrapped in a payload field.
The alternative, flattening the JSON into bracketed fields like history[0][lat], was deliberately avoided. It emits one field per leaf value, and a two-hour flight produces over 1300 of them. PHP parses a multipart body itself, so max_input_vars (default 1000) applies and silently discards everything past the limit. A JSON body is read in userland and is exempt. Under that encoding, attaching a file to a long flight would have quietly truncated the route, the flight log and the attachment references themselves, leaving the uploaded files with nothing pointing at them.

What your backend needs to do

  1. Accept multipart/form-data on the PIREP completion endpoint, alongside the application/json it already accepts, and read the PIREP from the payload field with a JSON decode.
  2. Walk the decoded body for $file references and match each { "$file": "<part name>" } object to the uploaded file part of that name, to reassemble which attachment belongs where.
  3. Check max_file_uploads (PHP default 20) if you expect plugins to attach many files at once. The PIREP itself is one field regardless of flight length, so max_input_vars is not a concern, but each file is still a separate upload.
  4. Return a status you can stand behind if you reject the submission. See the fallback below for which statuses Stratos treats as “reject the attachments” versus “something went wrong.”
  5. Accept a chunked request body. Because the submission is streamed, Stratos sends no Content-Length header, so the request arrives chunked. A proxy or web server in front of your backend that refuses chunked uploads will reject it with 411 or 400 before your application code ever sees it.

Requirements

File attachments require the flight-tracking plugin at version 1.8.0 or newer, which is the release that forwards a multipart submission to your backend. Stratos checks this itself: a pilot whose flight-tracking plugin is older simply submits on the JSON path with the attachments omitted, and the client logs why. Your backend never receives a multipart submission from a client that cannot complete one.

The fallback

If your backend rejects a multipart submission with 400, 413, 415, or 422, Stratos retries once, automatically, as a plain JSON submission with every attachment stripped out. The flight still files, and the pilot sees a warning that the attachments were rejected. Any other response, a 5xx, a timeout, or a transport failure, does not trigger this retry. Those are ambiguous about whether your backend already committed the PIREP, so retrying could re-file a flight you already accepted. They fall through to Stratos’s existing submission rollback instead, and the pilot retries manually. In short: if you’re rejecting a submission specifically because of its attachments (too large, wrong type, a validation rule on the attached fields), use 400/413/415/422 so the pilot’s flight still files. Reserve 5xx for genuine backend failures.