# File Operations

Process inbound Fedwire files and generate outbound ones

## Overview

Every Fedwire message is one ISO 20022 envelope in one file. Twisp processes the files you receive one at a time. It writes the messages you send as separate files in one tar.gz archive per generation.

| Operation | GraphQL                                                                                        | What it does                                       |
| --------- | ---------------------------------------------------------------------------------------------- | -------------------------------------------------- |
| Upload    | [`Mutation.files.createUpload()`](https://www.twisp.com/docs/reference/graphql/mutations.md#files.create-upload)                            | Get a URL to upload an inbound file                |
| Process   | [`Mutation.wire.processFile()`](https://www.twisp.com/docs/reference/graphql/mutations.md#wire.process-file)                                | Record an inbound message and apply it             |
| Generate  | [`Mutation.wire.generateFile()`](https://www.twisp.com/docs/reference/graphql/mutations.md#wire.generate-file)                              | Write every queued outbound message to one archive |
| Download  | [`Mutation.files.createDownload()`](https://www.twisp.com/docs/reference/graphql/mutations.md#files.create-download)                        | Fetch a generated archive                          |
| Inspect   | [`Query.wire.file()`](https://www.twisp.com/docs/reference/graphql/queries.md#wire.file), [`Query.wire.message()`](https://www.twisp.com/docs/reference/graphql/queries.md#wire.message) | Read a file, its messages and their outcome        |

## Processing Inbound Files

### 1. Upload

Upload the file exactly as the sponsor bank delivered it, with upload type `WIRE` and content type `application/xml`:

```graphql
mutation UploadWireFile {
  files {
    createUpload(
      input: {
        key: "wires/inbound/20261009-000123.xml"
        uploadType: WIRE
        contentType: "application/xml"
      }
    ) {
      uploadURL
      uploadHeaders
    }
  }
}
```

PUT the file to `uploadURL` with `uploadHeaders`.

### 2. Process

```graphql
mutation ProcessWireFile {
  wire {
    processFile(
      input: {
        configId: "5d1c6a0e-8b7f-4e2a-9c3d-2e1f0a9b8c7d"
        fileKey: "wires/inbound/20261009-000123.xml"
      }
    ) {
      fileId
      status
      messages(first: 1) {
        nodes {
          messageId
          kind
          msgId
          uetr
          status
        }
      }
    }
  }
}
```

`processFile` parses the envelope and records the file and its message, then returns. The message is applied asynchronously:

- A **credit transfer** (pacs.008, pacs.009) is decided and posted as an [inbound wire](https://www.twisp.com/docs/reference/wire/inbound.md).
- A **payment status** (pacs.002), **message reject** (admi.002) or **payment return** (pacs.004) is applied to the [outbound wire](https://www.twisp.com/docs/reference/wire/outbound.md#replies-from-the-fed) it answers.
- **Other messages**, such as a camt.056 return request or a Fed broadcast, are recorded with no action.

`processFile` fails with `BAD_REQUEST` when the file is not a Fedwire envelope from the Fed: the wrong root element or namespace, an outgoing-to-Fed envelope, no message or more than one, or an amount that is not a whole number of cents.

### Processing Twice

Processing is idempotent:

- Processing the same file key again returns the earlier file and changes nothing.
- A message already recorded under another key is recorded as a new file with no messages and a `detail` naming the earlier message. It is not applied again.
- A credit transfer whose UETR an earlier wire already has is recorded as `IGNORED`.

### Following a Message

Read the message to see its outcome:

```graphql
query WireMessage {
  wire {
    message(messageId: "0b9e44a2-6d0c-5b3e-9f1a-2c3d4e5f6a7b") {
      kind
      status
      detail
      executionId
    }
  }
}
```

| Message status | Meaning                                                                                                                     |
| -------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `RECEIVED`     | Recorded; not yet applied                                                                                                   |
| `PROCESSED`    | Applied: posted, or applied to its outbound wire. `detail` says how, such as `settle` or `returned AC03`.                   |
| `IGNORED`      | Recorded with no action: a duplicate, a reply for a wire that no longer accepts it, or a message type Twisp does not act on |
| `ERROR`        | Could not be applied. `detail` gives the reason.                                                                            |
| `GENERATED`    | An outbound message written to a generation's archive                                                                       |

`executionId` is the workflow execution the message belongs to. For an inbound credit transfer it is the `WIRE_INBOUND` execution, whose id equals the message id.

## Generating Outbound Files

Wires you send (`WIRE_OUTBOUND` executions in `CREATE`) and inbound wires you return wait in queues. `generateFile` writes them to one tar.gz archive at the `fileKey` you give:

```graphql
mutation GenerateWireFile {
  wire {
    generateFile(
      input: {
        configId: "5d1c6a0e-8b7f-4e2a-9c3d-2e1f0a9b8c7d"
        fileKey: "wires/outbound/2026-10-09T15-00.tar.gz"
      }
    ) {
      generated
      file {
        fileId
        fileKey
        messageCount
        totalAmount
        messages(first: 100) {
          edges {
            node {
              kind
              msgId
              uetr
            }
          }
        }
      }
    }
  }
}
```

For each queued item, `generateFile`:

1. Gives the message the next [IMAD](https://www.twisp.com/docs/reference/wire/configuration.md#message-ids-imad).
2. Builds a pacs.008 for an outbound wire, or a pacs.004 for a returned inbound wire, and checks it against the Fed usage guidelines.
3. Adds it to the archive as its own file.
4. Moves an outbound wire to `SUBMIT`.

Wires cancelled or rejected while queued are skipped. When nothing is queued, `generated` is `false` and no archive is written.

`generateFile` is idempotent by `fileKey`. If a key was already generated for the configuration, `generateFile` returns that generation and writes nothing. Use a new key for each generation, such as one with the time in it. If a call fails, or you do not get its response, call again with the same key: you get the generation if it was written, or a new one if it was not.

### Archive Layout

The archive holds one file per message, in IMAD order:

```
000001_customer_credit_transfer_<uetr>.xml
000002_customer_credit_transfer_<uetr>.xml
000003_payment_return_<uetr>.xml
```

Each file is one Fedwire envelope. Forward each one to the sponsor bank as its own file. The number at the start is the message's `position` plus one. The generation's messages, with their IMADs, UETRs and amounts, are also on [`Query.wire.file()`](https://www.twisp.com/docs/reference/graphql/queries.md#wire.file).

The archive is complete when `generateFile` returns. If a generation fails, its wires stay queued, and the next `generateFile` writes them with the same IMADs. A failed generation can leave an archive at its key. Do not forward it: the archive at a key is valid only after `generateFile` returns that key with `generated: true`. A retry with the key replaces the archive.

### Downloading

Get a presigned download URL for the archive with [`Mutation.files.createDownload()`](https://www.twisp.com/docs/reference/graphql/mutations.md#files.create-download):

```graphql
mutation DownloadWireFile {
  files {
    createDownload(key: "wires/outbound/2026-10-09T15-00.tar.gz") {
      downloadURL
      downloadURLExpiration
    }
  }
}
```

## File Statuses

| Status       | Meaning                                            |
| ------------ | -------------------------------------------------- |
| `PROCESSING` | An inbound file whose message is being applied     |
| `PROCESSED`  | An inbound file whose message has an outcome       |
| `ERROR`      | An inbound file whose message could not be applied |
| `GENERATED`  | An outbound generation whose archive is written    |
