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#