RoadOpsDeveloper

Day Files

Prepare a short-lived S3 upload, PUT the raw file with the returned headers, then complete the upload so RoadOps can verify and create the day file.

Ask

Upload workflow

Day file uploads use a three-step workflow so the file bytes travel directly to private S3 storage instead of passing through the Developer API:

  • Step 1: Call POST /v1/teams/{teamId}/days/{dayId}/files with the file name, MIME type, and exact byte size. The response reserves a fileId and returns upload.url, upload.headers, upload.expiresAt, completion.path, and completion.body.
  • Step 2: Send the raw file bytes to upload.url with PUT. Include every entry from upload.headers exactly as returned. Do not use multipart/form-data, and do not send the RoadOps Authorization header to S3. S3 must return a successful 2xx response before you continue.
  • Step 3: Call POST completion.path with your RoadOps bearer token and the returned completion.body. You may add optional visibility JSON alongside the required fileName. RoadOps verifies the stored object before accepting the day-file creation command. Use an Idempotency-Key UUID when retrying this completion request.

The presigned S3 URL is valid for 15 minutes and is limited to the reserved file, MIME type, byte length, and RoadOps metadata. Its expiration controls when S3 can accept the PUT; it does not delete an upload that S3 accepted before expiration. If the URL expires before the upload succeeds, repeat step 1. Supply and reuse an id in the prepare request when you want the replacement URL to target the same not-yet-completed file ID.

# Step 1: prepare the upload
curl -X POST "https://api.roadops.app/v1/teams/{teamId}/days/{dayId}/files" \
  -H "Authorization: Bearer ro_live_..." \
  -H "Content-Type: application/json" \
  -d '{"fileName":"Security map.pdf","contentType":"application/pdf","sizeBytes":245760}'

# Step 2: PUT the raw bytes to upload.url with every returned upload header
curl -X PUT "{upload.url}" \
  --upload-file "Security map.pdf" \
  -H "Content-Type: application/pdf" \
  -H "Content-Length: 245760" \
  -H "x-amz-meta-roadops-team-id: {returned value}" \
  -H "x-amz-meta-roadops-day-id: {returned value}" \
  -H "x-amz-meta-roadops-file-id: {returned value}" \
  -H "x-amz-meta-roadops-file-name: {returned value}"

# Step 3: complete the upload after S3 returns 2xx
curl -X POST "https://api.roadops.app{completion.path}" \
  -H "Authorization: Bearer ro_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 00000000-0000-4000-8000-000000000999" \
  -d '{"fileName":"Security map.pdf","visibility":{"userIds":["00000000-0000-4000-8000-000000000101"]}}'

List day files

GET/v1/teams/{teamId}/days/{dayId}/files

Lists files attached to a day.

Required scopes: day_file:read

Parameters

path.teamIdrequiredstring:uuid

Team identifier.

path.dayIdrequiredstring:uuid

Day identifier.

query.limitinteger

Maximum number of records to return. Defaults to 50 and is capped at 200.

query.cursorstring

Cursor returned by page.nextCursor from the previous list response.

Response 200

dataobject[]
data[].dayIdstring:uuid
data[].fileNamestring
data[].contentTypestring
data[].sizeBytesinteger
data[].visibilityobject
data[].visibility.groupIdsstring:uuid[]
data[].visibility.userIdsstring:uuid[]
data[].idstring:uuid
data[].orgIdstring:uuid
data[].isDeletedboolean
data[].versioninteger

Current day file aggregate version.

data[].createdAtstring:date-time

ISO 8601 timestamp when the day file was created.

data[].createdBystring

User identifier that created the day file.

data[].updatedAtstring:date-time

ISO 8601 timestamp when the day file was last updated.

data[].updatedBystring

User identifier that last updated the day file.

data[].deletedAtstring:date-time

ISO 8601 timestamp when the day file was deleted.

data[].cursorinteger

Latest event-ledger cursor projected into this day file record.

pageobject
page.limitinteger

Maximum number of records returned in this page.

page.nextCursorstring | null

Cursor to pass as the next request's cursor query parameter, or null when there are no more results.

metaobject
meta.cursornumber | null

Highest record cursor represented by this response.

Prepare a day file upload

POST/v1/teams/{teamId}/days/{dayId}/files

Step 1 of the day file upload workflow. Send JSON metadata to reserve a file ID and receive a presigned S3 URL that is valid for 15 minutes. Step 2: send the raw file bytes—not multipart/form-data—to upload.url with PUT, including every header in upload.headers exactly as returned. Do not send the RoadOps Authorization header to S3. After S3 returns a successful 2xx response, complete step 3 by calling the returned completion.path with your RoadOps bearer token and completion.body. If the upload URL expires before S3 accepts the file, call this endpoint again; callers that supplied id can reuse that ID until the upload is completed.

Required scopes: day_file:write

Parameters

path.teamIdrequiredstring:uuid

Team identifier.

path.dayIdrequiredstring:uuid

Day identifier.

Request body (application/json)

idstring:uuid

Optional caller-supplied day file ID. Reuse the same ID when requesting a replacement URL for an upload that has not been completed. RoadOps generates one when omitted.

fileNamerequiredstring

Original file name shown in RoadOps. Path separators and unsupported characters are removed before the upload target is created.

contentTypestring

MIME type to store with the object. Defaults to application/octet-stream. Send the returned Content-Type header unchanged in the S3 PUT request.

sizeBytesrequiredinteger

Exact file size in bytes. Day file uploads are limited to 10 MB. The S3 PUT request must send this exact number of bytes.

Response 200

dataobject
data.fileIdstring:uuid

ID reserved for the day file.

data.fileNamestring

Sanitized file name that RoadOps will display.

data.contentTypestring

MIME type that will be stored with the uploaded object.

data.sizeBytesinteger

Exact number of bytes expected by the signed upload request.

data.uploadobject
data.upload.method"PUT"

HTTP method required when sending the file directly to S3.

data.upload.urlstring:uri

Short-lived presigned S3 URL. Send the raw file bytes to this URL; do not use multipart/form-data.

data.upload.expiresAtstring:date-time

ISO 8601 time when S3 stops accepting new upload requests through this URL.

data.upload.expiresInSecondsinteger

Number of seconds the presigned URL remains valid after issuance.

data.upload.headersobject

Complete set of required headers for the S3 PUT request. Send every header exactly as returned and do not send the RoadOps Authorization header to S3.

data.upload.headers.Content-Typestring

Required MIME type signed into the upload URL. Send this exact value.

data.upload.headers.Content-Lengthstring

Required decimal byte length signed into the upload URL. Send this exact value.

data.upload.headers.x-amz-meta-roadops-team-idstring:uuid

Required signed RoadOps team metadata. Send this exact value.

data.upload.headers.x-amz-meta-roadops-day-idstring:uuid

Required signed RoadOps day metadata. Send this exact value.

data.upload.headers.x-amz-meta-roadops-file-idstring:uuid

Required signed RoadOps file metadata. Send this exact value.

data.upload.headers.x-amz-meta-roadops-file-namestring

Required signed RoadOps file-name metadata. Send this exact value.

data.completionobject
data.completion.method"POST"

HTTP method used to complete the upload with RoadOps.

data.completion.pathstring

Developer API path to call after S3 returns a successful 2xx response.

data.completion.bodyobject

Required fields to include in the completion request body.

data.completion.body.fileNamestring

Sanitized file name to send unchanged in the completion request body.

Complete a day file upload

POST/v1/teams/{teamId}/days/{dayId}/files/{fileId}/complete

Step 3 of the day file upload workflow. Call this endpoint only after the PUT to the presigned S3 URL returns a successful 2xx response. Send the sanitized fileName returned by the prepare call unchanged in this request body. RoadOps uses it to locate the private object, then verifies its signed RoadOps metadata and the 10 MB size limit before accepting the DayFile.Create command. The presigned URL's 15-minute expiration only limits when S3 can accept the PUT; an object uploaded before expiration can still be completed afterward. Use an Idempotency-Key when retrying this completion request, then poll the receipt's links.refetch URL until the returned cursor reaches the receipt cursor.

Required scopes: day_file:write

Parameters

path.teamIdrequiredstring:uuid

Team identifier.

path.dayIdrequiredstring:uuid

Day identifier.

path.fileIdrequiredstring:uuid

Reserved day file identifier returned by the prepare call.

header.Idempotency-Keystring:uuid

Optional caller-generated UUID used to make mutation retries safe. Reuse the same UUID only when retrying the same request.

Request body (application/json)

fileNamerequiredstring

Sanitized file name returned by the prepare call. Send it unchanged so RoadOps can verify the uploaded S3 object.

visibilityobject

Optional visibility rules applied when the uploaded object becomes a day file record.

visibility.groupIdsstring:uuid[]
visibility.userIdsstring:uuid[]

Response 202

commandIdstring:uuid

Command identifier accepted into the RoadOps command ledger.

resourceIdstring:uuid

Identifier of the resource targeted by the command.

resourceTypestring

Resource type targeted by the command.

status"applied"

Command dispatch status.

aggregateVersioninteger

Aggregate version produced by the accepted command.

cursorinteger

Event-ledger cursor for the accepted update. Refetch until returned records reach this cursor.

linksobject
links.refetchstring

Relative URL to refetch the affected resource.

Retrieve a day file

GET/v1/teams/{teamId}/days/{dayId}/files/{fileId}

Returns one day file by ID.

Required scopes: day_file:read

Parameters

path.teamIdrequiredstring:uuid

Team identifier.

path.dayIdrequiredstring:uuid

Day identifier.

path.fileIdrequiredstring:uuid

Day file identifier.

Response 200

dataobject
data.dayIdstring:uuid
data.fileNamestring
data.contentTypestring
data.sizeBytesinteger
data.visibilityobject
data.visibility.groupIdsstring:uuid[]
data.visibility.userIdsstring:uuid[]
data.idstring:uuid
data.orgIdstring:uuid
data.isDeletedboolean
data.versioninteger

Current day file aggregate version.

data.createdAtstring:date-time

ISO 8601 timestamp when the day file was created.

data.createdBystring

User identifier that created the day file.

data.updatedAtstring:date-time

ISO 8601 timestamp when the day file was last updated.

data.updatedBystring

User identifier that last updated the day file.

data.deletedAtstring:date-time

ISO 8601 timestamp when the day file was deleted.

data.cursorinteger

Latest event-ledger cursor projected into this day file record.

metaobject
meta.cursorinteger

Highest record cursor represented by this response.

Update a day file

PATCH/v1/teams/{teamId}/days/{dayId}/files/{fileId}

Accepts a command to update day file metadata. File replacement is not supported; delete and upload a new file instead.

Required scopes: day_file:write

Parameters

path.teamIdrequiredstring:uuid

Team identifier.

path.dayIdrequiredstring:uuid

Day identifier.

path.fileIdrequiredstring:uuid

Day file identifier.

header.Idempotency-Keystring:uuid

Optional caller-generated UUID used to make mutation retries safe. Reuse the same UUID only when retrying the same request.

Request body (application/json)

expectedVersioninteger

Optional aggregate version guard.

updatesrequiredobject

Day file fields to update.

updates.visibilityobject | null

Updated visibility, or null to clear custom visibility.

updates.visibility.groupIdsstring:uuid[]
updates.visibility.userIdsstring:uuid[]

Response 202

commandIdstring:uuid

Command identifier accepted into the RoadOps command ledger.

resourceIdstring:uuid

Identifier of the resource targeted by the command.

resourceTypestring

Resource type targeted by the command.

status"applied"

Command dispatch status.

aggregateVersioninteger

Aggregate version produced by the accepted command.

cursorinteger

Event-ledger cursor for the accepted update. Refetch until returned records reach this cursor.

linksobject
links.refetchstring

Relative URL to refetch the affected resource.

Delete a day file

DELETE/v1/teams/{teamId}/days/{dayId}/files/{fileId}

Accepts a command to delete a day file and remove the stored object.

Required scopes: day_file:write

Parameters

path.teamIdrequiredstring:uuid

Team identifier.

path.dayIdrequiredstring:uuid

Day identifier.

path.fileIdrequiredstring:uuid

Day file identifier.

header.Idempotency-Keystring:uuid

Optional caller-generated UUID used to make mutation retries safe. Reuse the same UUID only when retrying the same request.

Response 202

commandIdstring:uuid

Command identifier accepted into the RoadOps command ledger.

resourceIdstring:uuid

Identifier of the resource targeted by the command.

resourceTypestring

Resource type targeted by the command.

status"applied"

Command dispatch status.

aggregateVersioninteger

Aggregate version produced by the accepted command.

cursorinteger

Event-ledger cursor for the accepted update. Refetch until returned records reach this cursor.

linksobject
links.refetchstring

Relative URL to refetch the affected resource.

Download a day file

GET/v1/teams/{teamId}/days/{dayId}/files/{fileId}/download

Redirects to a short-lived signed URL that downloads the file directly. Follow the redirect or read the Location response header.

Required scopes: day_file:read

Parameters

path.teamIdrequiredstring:uuid

Team identifier.

path.dayIdrequiredstring:uuid

Day identifier.

path.fileIdrequiredstring:uuid

Day file identifier.