File Attachments

Images, documents, audio, and video move through Outeract as File records connected to message events by attachment edges. This guide covers the file lifecycle: receiving files on inbound messages, uploading your own, sending them with sendMessage, and reading the bytes back out.

File operations run on the developer API at https://api.outeract.com/ (Authorization: Bearer <api-key>). Creating files requires the events:write scope.

The File Model

FieldTypeDescription
idUUIDUnique identifier
appIdUUIDApplication this file belongs to
urlstringClient-usable URL (GCS references resolve to time-limited signed URLs)
dataUristringInline data:<mime>;base64,... URI of the file contents (on demand)
storageTypeenumEMBEDDED (data URI in the database) or GCS (Google Cloud Storage / external URL)
statusstringProcessing status (completed for usable files)
mimeTypestringMIME type, e.g. image/png
sizeintSize in bytes
filenamestringOriginal filename, if known
descriptionstringOptional caption / description
createdAtdatetimeWhen the record was created

The raw storage reference is internal. Clients always read url (resolved at query time) or dataUri (bytes inlined at query time).

Lifecycle

flowchart LR
    subgraph Inbound
        W["Platform webhook<br/>(media message)"] --> F1["File record"]
        F1 -->|attachment edge| E1["message.inbound event"]
    end
    subgraph Outbound
        U["createFile mutation"] --> F2["File record"]
        F2 -->|fileIds| S["sendMessage"]
        S -->|attachment edge| E2["message.outbound event"]
        E2 --> P["Delivered to platform"]
    end

Files on Inbound Messages

When a user sends media on a connected platform, Outeract stores the content as a File and links it to the message.inbound event with an attachment edge. Read attachments straight off the event:

query {
  events(first: 20, eventTypes: ["message.inbound"]) {
    edges {
      node {
        id
        eventType
        payload
        attachments {
          id
          mimeType
          size
          filename
          url
        }
      }
    }
  }
}

Uploading Files

createFile mints a File record you can then attach to outgoing messages. Provide exactly one of data or url:

```graphql mutation { createFile( mimeType: "image/png" data: "iVBORw0KGgoAAAANSUhEUg..." filename: "receipt.png" description: "January receipt" ) { id url storageType size } } ``` `data` accepts a bare base64 string or a full `data:image/png;base64,...` URI. The bytes are stored via the configured storage backend: Google Cloud Storage when configured, otherwise embedded in the database as a data URI.
```graphql mutation { createFile( mimeType: "application/pdf" url: "https://example.com/report.pdf" filename: "report.pdf" ) { id url storageType } } ``` An `http(s)` URL is stored **by reference**, and the bytes are downloaded at send time. A `data:` URI passed as `url` is stored embedded.

The mutation returns the File with status: "completed", ready to send. Passing both data and url (or neither) is an error, as is invalid base64.

Sending Files

Pass file IDs to sendMessage. The message text is optional when attachments are present:

mutation {
  sendMessage(
    recipientUserId: "user-uuid"
    message: "Here is your receipt"
    fileIds: ["file-uuid"]
  ) {
    id
    eventType
    attachments {
      id
      mimeType
    }
  }
}

For each file, Outeract creates an attachment edge from the outbound message event to the File, then delivers the media through the platform integration.

A `sendMessage` call must include at least one of `message`, `template`, or `fileIds`.

Downloading and Accessing Content

By URL

url is the cheap path. Embedded data URIs and external URLs pass through unchanged; GCS-stored files resolve to a time-limited signed HTTPS URL at query time, so fetch it promptly rather than persisting it.

query {
  files(fileIds: ["file-uuid"]) {
    id
    mimeType
    size
    url
  }
}

Inline with dataUri

dataUri returns the file contents as a data:<mime>;base64,... string regardless of where the file is stored: embedded files return their stored URI directly, while GCS-stored and externally hosted files are fetched and encoded on demand. This is convenient when the consumer can’t follow URLs (e.g. feeding an image directly to an AI model).

query {
  files(fileIds: ["file-uuid"]) {
    id
    mimeType
    dataUri
  }
}
`dataUri` is only computed when your query selects it, but it inlines the entire file into the GraphQL response. Prefer `url` unless you actually need the bytes in-band, and avoid selecting `dataUri` across large file lists.

If the underlying bytes can’t be fetched (e.g. an unreachable external URL), dataUri returns null.

Attachment Edges in the Graph

Attachments are ordinary graph edges: source is the message event, target is the file, edgeType is attachment. That means you can traverse them like any other relationship:

# All attachment edges for a batch of events
query {
  edges(eventIds: ["event-uuid-1", "event-uuid-2"], edgeType: "attachment") {
    sourceNodeId
    targetNodeId
    edgeType
  }
}

You can also filter the event stream by a related file. For example, every event that references a given file:

query {
  events(
    relatedNodeId: "file-uuid"
    relatedNodeType: "file"
    relatedEdgeType: "attachment"
  ) {
    edges {
      node {
        id
        eventType
        createdAt
      }
    }
  }
}

See Also