Skip to main content

Upload a support file (v1)

The v1 upload endpoint is the canonical way to attach a document to a charge or an account when your system already knows what type of document it is. RCM stores the file in S3 and immediately hands it to DVS for content validation against the payer's rule set.

If you don't know the document type, see the v2 upload guide.

Prerequisites

  1. 1An access token

    OAuth2 client credentials grant — see Obtaining tokens.

  2. 2An existing account

    Accounts originate from your HIS/PMS as origin events. You need the account_id (UUID).

  3. 3(Optional) An existing charge

    If the document supports a specific charge (a TISS guide for one surgery, an OPME request for materials of one procedure), pass its charge_code. Omit it for documents that support the whole account.

  4. 4A webhook receiver configured

    The result arrives by webhook, not in the upload response. If your receiver isn't set up yet, follow Implement a webhook receiver first or you'll have no way to see the outcome.

Endpoint

POST /v1/support-files/upload
Content-Type: multipart/form-data
Authorization: Bearer <token>

The request is a multipart with two parts:

Part 1 — file

The document itself. PDF or image (JPEG, PNG). Up to 200 MB.

Part 2 — request_data (JSON)

A JSON payload describing what the file documents:

{
"support_file_code": "TISS_GUIDE",
"authorization_id": "AUTH-2026-0042",
"authorized_by": "operator@hospital.example",
"support_file_metadata": {
"scanned_at": "2026-06-25T13:50:00Z",
"source": "scanner-emergency-room-3"
},
"events": [
{
"origin_event_id": "EVT-12345",
"agreement_code": "DEFAULT",
"charge_codes": ["CHG-001", "CHG-002"]
}
]
}
FieldRequiredDescription
support_file_codeyesThe document type. One of the catalogue codes configured for your (country, agreement_code): TISS_GUIDE, MEDICAL_ORDER, OPME_REQUEST, ANATOMIA_PATOLOGICA, etc.
authorization_idnoPre-authorisation number from the payer, when applicable.
authorized_bynoWho authorised the document on the provider side. Free-form, for audit.
support_file_metadatanoFree-form {key: value} for fields specific to your billing flow. Stored alongside the file, returned in webhook events.
eventsyesArray of one or more event linkages. Each entry binds the file to an event and optionally to specific charges of that event.
events[].origin_event_idyesThe event this file documents. Same origin_event_id you sent on the charge POST.
events[].agreement_codenoThe agreement under which the file is being submitted. Defaults to the agreement of the event.
events[].charge_codesnoSpecific charges this file backs. Omit to attach at event level (the file supports the whole encounter, not a specific procedure).

Multiple events entries let one upload back several events at once (rare but useful when a single document covers a whole hospital stay across atendimentos).

Full schema in the API reference.

Walkthrough

1. curl

The fastest way to verify everything works. Note that the request_data part is a JSON string with ;type=application/json so curl sets the per-part Content-Type correctly:

curl -X POST https://sandbox.osigu.com/rcm/v1/support-files/upload \
-H "Authorization: Bearer $TOKEN" \
-F "file=@./guia_tiss_2026_06_25.pdf" \
-F 'request_data={
"support_file_code": "TISS_GUIDE",
"events": [
{
"origin_event_id": "EVT-12345",
"agreement_code": "DEFAULT",
"charge_codes": ["CHG-001"]
}
]
};type=application/json'

Response (204 No Content). The endpoint acknowledges by returning 204; no body. Persist your local linkage between the file you uploaded and your origin_event_id — the validation outcome arrives via webhook referencing the same origin_event_id.

2. Node.js

import fs from 'node:fs';
import FormData from 'form-data';
import fetch from 'node-fetch';

async function uploadSupportFile({token, filePath, supportFileCode, originEventId, agreementCode, chargeCodes}) {
const form = new FormData();
form.append('file', fs.createReadStream(filePath));
form.append('request_data', JSON.stringify({
support_file_code: supportFileCode,
events: [
{
origin_event_id: originEventId,
agreement_code: agreementCode,
...(chargeCodes?.length ? {charge_codes: chargeCodes} : {}),
},
],
}), {contentType: 'application/json'});

const res = await fetch('https://sandbox.osigu.com/rcm/v1/support-files/upload', {
method: 'POST',
headers: {Authorization: `Bearer ${token}`},
body: form,
});

if (res.status !== 204) {
throw new Error(`Upload failed: ${res.status} ${await res.text()}`);
}
}

3. Python

import json
import requests

def upload_support_file(token, file_path, support_file_code, origin_event_id, agreement_code, charge_codes=None):
request_data = {
'support_file_code': support_file_code,
'events': [
{
'origin_event_id': origin_event_id,
'agreement_code': agreement_code,
**({'charge_codes': charge_codes} if charge_codes else {}),
}
],
}
with open(file_path, 'rb') as f:
files = {
'file': (file_path, f),
'request_data': (None, json.dumps(request_data), 'application/json'),
}
r = requests.post(
'https://sandbox.osigu.com/rcm/v1/support-files/upload',
headers={'Authorization': f'Bearer {token}'},
files=files,
timeout=60,
)
r.raise_for_status()

4. Java (Spring RestClient)

import org.springframework.http.MediaType;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClient;
import org.springframework.core.io.FileSystemResource;
import java.io.File;
import java.util.List;
import java.util.Map;

public void uploadSupportFile(
RestClient client, String token,
File file, String supportFileCode, String originEventId, String agreementCode, List<String> chargeCodes) {

Map<String, Object> requestData = Map.of(
"support_file_code", supportFileCode,
"events", List.of(Map.of(
"origin_event_id", originEventId,
"agreement_code", agreementCode,
"charge_codes", chargeCodes
))
);

HttpHeaders jsonHeaders = new HttpHeaders();
jsonHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Map<String, Object>> requestDataPart = new HttpEntity<>(requestData, jsonHeaders);

MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("file", new FileSystemResource(file));
body.add("request_data", requestDataPart);

client.post()
.uri("https://sandbox.osigu.com/rcm/v1/support-files/upload")
.header("Authorization", "Bearer " + token)
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(body)
.retrieve()
.toBodilessEntity();
}

What happens next

The upload returns 202 and your support_file_id — DVS processes the file in the background. Within 10–30 seconds you'll receive one of two webhook events:

EventMeaningCustomer action
DOCUMENT_VALIDATION_COMPLETEDDocument passed every rule (validation_status: APPROVED).The charge is now eligible to advance to invoicing.
DOCUMENT_VALIDATION_REJECTEDOne or more rules failed (validation_status: REJECTED). The errors array lists each failure (field, reason, subreason, reason_key).Show the errors to the end user; they correct the document and you re-upload.

See Webhook events → Validation for the full payload shape.

Common errors

StatusBody excerptWhat went wrong
400 Bad Request"support_file_code is required"You're hitting the v1 endpoint without support_file_code. Use v2 if you want auto-classification.
400 Bad Request"unknown support_file_code"The code you passed isn't in the catalogue for your (country, agreement_code). Confirm with OSIGU which codes you're authorised to upload.
401 Unauthorized"invalid token"Token expired or you're using one from a different environment. Refresh.
403 Forbidden(permission denied)Your client_id isn't authorised to upload support files. Contact OSIGU support.
404 Not Found"account not found"The account_id doesn't exist in your provider's scope. Wrong UUID, or account belongs to a different provider.
406 Not Acceptable(no body)File exceeds 200 MB. Compress or split.
422 Validation error"charge_code does not belong to account_id"The charge isn't in the account you specified. Pass either matching values, or drop charge_code to attach at account level.

Edge cases

  • Re-uploading the same file for the same (account_id, charge_code, support_file_code) is allowed — RCM stores both versions and runs validation on the new one. The previous file is marked superseded but kept for audit.
  • Multiple support files of the same type on the same charge — also allowed. Each is independently validated.
  • No charge_code but the agreement requires per-charge documentation — the upload succeeds and the file gets APPROVED, but the charge is flagged at invoice time as missing required documents. Always pass charge_code when the document is charge-specific.
  • Network interruption mid-upload — the request is not retried by RCM. Re-send the multipart from scratch with the same parameters. If a partial upload already produced a support_file_id, you'll end up with two records — they're independent and DVS validates both.

Local debugging

For end-to-end testing in sandbox without a real public webhook URL, see Verify HMAC signatures — it includes an ngrok-based loop.