Skip to main content

Run the off-site archive

Core module For: Administrator, Compliance manager Checked on 18.0.0.2.0

Once the off-site archive (also written offsite) is on, every posted DSCSA document and every retention export is queued and sent to the S3 bucket by a scheduled job. Check the queue now and then, fix the uploads that fail, queue the records that were posted before the archive was switched on, and get a record back from the bucket and check it outside Odoo when you need it. To set the archive up, see Set up the off-site archive.

Watch the archive queue and each record's archive state​

See what is waiting to be sent, what was sent and what failed, and whether a given document or retention export has its copy in the bucket.

Who: Rx Tracking Manager

Requires: Rx Tracking (DSCSA). Works in Odoo Community and Enterprise.

Before you start:

  1. Go to Inventory ‣ Rx Tracking ‣ S3 Archive Queue.

    Result: the queue opens with the filters Pending and Failed, newest first. Each row is one object to write: S3 Key, Kind, DSCSA Document, Attempts, Next Attempt, Sent On, Object Lock Mode, Last Error and State. Failed rows are red. With nothing to show, the list reads "Nothing in the S3 archive queue".

  2. To keep each row on one line, hide the Last Error column: select the column selector (the icon at the right end of the list heading) and clear Last Error. A failed row's error is long (see the known issue below); you read it on the job's form.

  3. To see failed rows first, select the State column heading.

    Screenshot of the S3 Archive Queue sorted by State: five Failed jobs in red, then Pending jobs with their next attempt time.

  4. To narrow the list, open the search options (the ▾ next to the search box) and choose a filter: Pending, Failed, Done, Documents, Retention Exports, Legal Holds or Test Mode (Governance); or group by Status, Kind or Object Lock Mode. To see every job, remove the Pending or Failed filter.

  5. Open a job.

    Result: the job form shows the S3 Key, and:

    • Object: Kind, the DSCSA Document or Retention Export, the File, Content Type, Size (Bytes), SHA-256, Object Lock Mode and Object Lock Until (the retention date);
    • Sending: Attempts, Next Attempt (while pending), Last Attempt, and once sent HTTP Status, Sent On, S3 Version ID and ETag;
    • Last Error, when the last attempt failed.
  6. To check one document, go to Inventory ‣ Rx Tracking ‣ Documents, open the document and select its Off-site Archive tab.

    Result: S3 Archive shows the document's state, with Archived On and the S3 Manifest Key, S3 Manifest Version ID and S3 Manifest ETag once it is archived. Below them, the tab lists the document's jobs.

    Screenshot of a posted document's Off-site Archive tab: S3 Archive Failed, one Pending job and one Failed job with its Last Error.

Result: you know what is still to send and what failed. Records: nothing changes; the queue and the tab only show.

To list the documents without a finished copy, open Inventory ‣ Rx Tracking ‣ Documents and choose the filter Not Archived Off-site (or Off-site Archive Failed). The list's optional S3 Archive column shows the state. A retention export shows the same information in its Off-site Archive group, and the Retention Exports list has an S3 Archive column.

What the jobs and states mean:

Job StateMeaning
PendingWaiting to be sent. After a failed attempt it waits until Next Attempt
DoneS3 accepted the object; Sent On, S3 Version ID and ETag are filled in
FailedSending stopped after the last attempt, or at once for a failure that can't be retried. See Fix and retry failed archive uploads
SupersededNot sent and no longer needed: a test upload when test mode was switched off, or a legal-hold change replaced by a newer one
Job KindWhat it writes
Document fileone stored file of a posted document: the T3 report (PDF), our EPCIS file, the supplier's original file
Document manifesta JSON file with the document's number, hash, previous hash and the SHA-256 of each file
Retention export fileone CSV file of a retention export
Legal holdplaces or releases the legal hold on an object already in the bucket (sent after its upload is Done)
Record S3 ArchiveMeaning
Not archivednothing is queued for it: posted before the archive was on, or its test uploads were superseded (see ARC-07)
Queuedits uploads are queued or being retried
Archivedall its compliance uploads are Done
Failedone of its uploads is Failed
Test archive onlyits only uploads were made in test mode: it is queued again after test mode is switched off

Keys follow a fixed pattern: KEY-PREFIX/company-COMPANY-ID/documents/YEAR/DOCUMENT-NUMBER/FILE (plus DOCUMENT-NUMBER.manifest.json; in keys, each / of the document number is a -, as in DSCSA-OUT-2026-00002), and KEY-PREFIX/company-COMPANY-ID/exports/YEAR/monthly-YYYY-MM/FILE (manual-NN for an on-demand export). Test uploads have test/ after the company folder.

With the 3PL add-on: documents issued in an owner's name are archived with your company's, under the same prefix and in the same hash chain: their keys differ only by the owner's code in the document number (…/documents/2026/DSCSA-NWG-OUT-2026-00001/…). There is no folder per owner (a per-owner prefix is not built), so an owner's records are found by that code. Only DSCSA documents and retention exports go to the archive: owner notices, owner holds, title transfers and owner exports stay in Odoo. Which 3PL records are kept, and the owner column of the retention export: 3PL records that are kept and The owner column of the retention export.

Jobs are written only by the archive itself: nobody can create, change or delete them, and the archive fields of a document can't be edited.

Known issue

Known issue (PF-W11-04): when S3 can't be reached, Last Error holds a long technical text ("No reply from S3 (ConnectionError): HTTPConnectionPool(…) …") that makes each row several lines tall. Read its first words, or open the job to read it whole; to keep the list compact, hide the Last Error column (step 2).

If it doesn't work

Fix and retry failed archive uploads​

When an upload fails, the archive retries it by itself: after 15 minutes, then after twice as long each time, up to once a day. After the eighth attempt the job is Failed, and every Rx Tracking Manager of the company gets an S3 archive failed activity. Find the cause in Last Error, fix it, then select Retry.

Who: Rx Tracking Manager; fixing the archive settings needs an administrator with Administration: Settings who is also Inventory Administrator and Rx Tracking Manager

Requires: Rx Tracking (DSCSA). Works in Odoo Community and Enterprise.

Before you start:

  1. Go to Inventory ‣ Rx Tracking ‣ S3 Archive Queue and open a Failed job.

    Or start from the activity: in the top bar, select the Activities icon (the clock), then DSCSA S3 Archive Job, and open the job listed. Its chatter shows the activity with the first failed key and error, and "Filter the S3 Archive Queue on Failed, fix the cause, then Retry.".

  2. Read the Last Error.

    Screenshot of a failed S3 archive job: the Retry button, the Failed status, attempts 1 and the Last Error "No reply from S3 (ConnectionError)".

  3. Fix the cause. The most frequent ones:

    Last Error starts withCauseFix
    "No reply from S3"the server couldn't reach S3: network, firewall, wrong Endpoint URL, S3 unavailablecheck the network and the Endpoint URL (empty for AWS)
    "The S3 archive settings of COMPANY are incomplete:"a setting it names is empty, often the secret keyfill it in (ARC-01)
    "HTTP 403 Forbidden: SignatureDoesNotMatch"wrong secret access keyenter the right key (ARC-01)
    another "HTTP …" message, for example "HTTP 403 Forbidden: InvalidAccessKeyId"wrong access key ID, IAM policy, bucket or region: the text after the status is S3's own error code and messagecheck them with your AWS administrator
    "S3 already holds an object under this key, and this job never stored one"another database writes to the same key prefixgive this database its own Key prefix; the job stays Failed
    "The content of KEY no longer matches the SHA-256"the stored file changed since it was queuednot sent on purpose: run Verify Integrity (REC-10)

    Every message is explained in Troubleshooting: off-site archive and scheduled jobs.

  4. Go back to the queue, keep the Failed filter, and select the failed jobs (the checkbox in the column heading selects all of them).

  5. Select Retry (above the list).

    Result: the notification "S3 archive" says "N archive job(s) queued again.". The jobs are Pending with Attempts 0, and the S3 archive failed activities on them are marked done with "Retried by USER.".

  6. Wait for the next hourly run, or ask the administrator to run DSCSA: send S3 archive queue by hand (Check that a DSCSA job ran, and run it by hand).

    Result: the jobs are Done. If they fail again, their Last Error says why.

Result: the failed uploads are queued again. Records: the jobs (Pending, then Done), and in the chatter of the job that carried it, the activity marked done with its feedback.

You can also select Retry on one failed job's form, and on Pending jobs, to send them at the next run instead of waiting for Next Attempt.

Known issue

Known issue (PF-W11-02): the S3 archive failed activity sits on the first job that failed. Retrying only that job marks the activity done for every manager, even when other jobs are still Failed; retrying other jobs leaves it open. Before you mark the work finished, check that the Failed filter shows no rows.

Known issue (PF-V02e-02): the S3 archive failed activity is due on the date of OdooBot, which runs the scheduled job in another time zone. In the United States, a failure in the afternoon or evening shows as Future (due tomorrow) in the Activities menu, not Today. Check the queue's Failed filter each day instead of waiting for the activity to be due.

If it doesn't work

  • "Only failed or pending archive jobs can be retried.": the selection has only Done or Superseded jobs. Select Failed or Pending ones. See Error: "Only failed or pending archive jobs can be retried.".
  • The same error comes back: the cause isn't fixed. Read the Last Error again and see its troubleshooting entry.

Queue records posted before the archive was switched on​

The archive queues each document when it is posted and each export when it is made. Documents and exports that already existed when you switched the archive on, and records archived only in test mode, need to be queued once.

Known issue

Known issue (PF-W11-01): the queue's Queue Unarchived Records button ends with the dialog "Oops! Something went wrong…" and queues nothing. Until it is fixed, an administrator creates the action below once, and a manager runs it from the Documents list.

Who: part 1: Administrator with Administration: Settings; part 2: Rx Tracking Manager

Requires: Rx Tracking (DSCSA). Works in Odoo Community and Enterprise.

Before you start:

  • The archive is on for the company, in the mode you want (for compliance uploads, test mode is off). See Set up the off-site S3 archive.
  • For part 1, developer mode: go to Settings, scroll to Developer Tools, and select Activate the developer mode.

Part 1: create the action (once)​

  1. Go to Settings ‣ Technical ‣ Server Actions and select New.

  2. Fill in:

    • the name (placeholder "e.g. Mass archive contacts"): Queue unarchived DSCSA records (S3)
    • Model: DSCSA Transaction Document
    • Type: Execute Code
  3. In Code, replace the text with this line:

    action = env['dscsa.archive.job'].action_dscsa_queue_unarchived()
  4. Save the record (the ☁ Save manually icon).

  5. Select Create Contextual Action.

    Result: the button changes to Remove Contextual Action. The action is now in the ⚙ (Actions) menu of the documents list.

Part 2: queue the records​

  1. Go to Inventory ‣ Rx Tracking ‣ Documents.

  2. Select one document (its checkbox). Which one doesn't matter: the action always looks at every posted document and export.

  3. Open the ⚙ (Actions) menu and select Queue unarchived DSCSA records (S3).

    Result: the notification "S3 archive" says "N document(s) and M retention export(s) queued.", for example "12 document(s) and 1 retention export(s) queued.".

Result: every posted document and retention export of the selected companies with the archive on that had no compliance upload is queued: new jobs in Inventory ‣ Rx Tracking ‣ S3 Archive Queue, and S3 Archive Queued on those records. In test mode, records never queued get test uploads instead. Running the action again queues nothing new. Records: one Pending job per file and one manifest job per document.

If it doesn't work

Get a record back from the off-site archive and check it​

Download a document's files and its manifest from the bucket, and prove outside Odoo that they are the ones Odoo posted: when an auditor or an inspector asks for a record, when Odoo is unavailable, or to test your recovery procedure. Each document's manifest carries its hash and the exact data that was hashed, so you can recompute the hash and follow the chain to the document before it without Odoo. Background: the archive keeps a write-once copy of every document in a bucket you control, and the documents form a hash-chained record. Your procedure decides who may download from the bucket and how often you rehearse this.

Who: Rx Tracking Manager for the keys in Odoo; for the download, a person with read access to the bucket (an AWS user allowed s3:GetObject and s3:GetObjectVersion, for example your AWS administrator). The checks themselves need no access.

Requires: Rx Tracking (DSCSA). Works in Odoo Community and Enterprise. The off-site archive, set up as in Set up the off-site S3 archive. For the checks, a computer with Python 3.

Before you start:

Note

Part 2 was not run end to end while this page was written: the example database has no real bucket. Parts 1, 3 and 4 were performed on the example database, with the manifest copied from its archive job and the files downloaded from the document (the rehearsal in part 3's first paragraph).

Part 1: find the keys​

  1. Go to Inventory ‣ Rx Tracking ‣ Documents and open the document.

  2. In the Integrity group, note the Chain Position and the Hash, for example position 8 and $1$efd60579345ae3afac3277bf61aa11fafb911ca66648e7da2de66338acee6ed8.

  3. Select the Off-site Archive tab.

    Result: S3 Manifest Key and S3 Manifest Version ID name the manifest object. The list below shows one job per file with its S3 Key and S3 Version ID, for example dscsa/company-1/documents/2026/DSCSA-OUT-2026-00002/T3-DSCSA-OUT-2026-00002.pdf.

Without Odoo, build the keys from the pattern of ARC-05: KEY-PREFIX/company-COMPANY-ID/documents/YEAR/NUMBER/, where NUMBER is the document number with each / replaced by - (DSCSA-OUT-2026-00002) and YEAR the year of its Document Date. The folder holds the files and NUMBER.manifest.json. The archive never writes a key twice, so each key has one version.

Part 2: download the files and the manifest by version​

Choose one:

  • AWS console: open the bucket, go to the document's folder, select Show versions, select each object's version and Download.

  • AWS command line: for the manifest and then for each file, run

    aws s3api get-object --bucket BUCKET-NAME --key S3-KEY --version-id VERSION-ID FILE-NAME

    To list the versions of a folder: aws s3api list-object-versions --bucket BUCKET-NAME --prefix FOLDER-KEY/.

Save each file under its name in the manifest (files[].name, for example T3-DSCSA-OUT-2026-00002.pdf) and the manifest as NUMBER.manifest.json, all in one folder.

Result: you have the document's files and its manifest, each exactly as it was sent.

Part 3: check the files and the hash​

To rehearse without a bucket, or while the upload is still Pending, take the same data from Odoo: download the files from the document's Files tab, and copy the manifest from the job Document manifest on the Off-site Archive tab (its Manifest field shows only in developer mode: add ?debug=1 after /odoo in the browser's address bar). Save the text as NUMBER.manifest.json.

  1. Save this script as check-manifest.py in the folder:

    import hashlib, json, sys

    manifest = json.load(open(sys.argv[1], encoding="utf-8"))
    doc = manifest["document"]
    payload = json.loads(doc["hash_payload"])
    recomputed = "$1$" + hashlib.sha256((doc["previous_hash"] + doc["hash_payload"]).encode("utf-8")).hexdigest()
    print("Document: ", doc["number"], "- chain position", doc["chain_index"])
    print("Stored hash: ", doc["hash"])
    print("Recomputed: ", recomputed, "OK" if recomputed == doc["hash"] else "MISMATCH")
    print("Previous hash:", doc["previous_hash"] or "(none: first document of the company)")
    for path in sys.argv[2:]:
    name = path.replace("\\", "/").rsplit("/", 1)[-1]
    actual = hashlib.sha256(open(path, "rb").read()).hexdigest()
    expected = payload["files"].get(name)
    print("File: ", name, "OK" if actual == expected else "MISMATCH (or not in this manifest)")
  2. In a terminal in that folder, run it with the manifest and the files (on Windows, py instead of python3):

    python3 check-manifest.py DSCSA-OUT-2026-00002.manifest.json T3-DSCSA-OUT-2026-00002.pdf EPCIS-DSCSA-OUT-2026-00002.xml

    Result:

    Document: DSCSA/OUT/2026/00002 - chain position 8
    Stored hash: $1$efd60579345ae3afac3277bf61aa11fafb911ca66648e7da2de66338acee6ed8
    Recomputed: $1$efd60579345ae3afac3277bf61aa11fafb911ca66648e7da2de66338acee6ed8 OK
    Previous hash: $1$f1c9e50b1850aabddf4eaa820a6c34f659416e99b31186b127cccf69e53f09b9
    File: T3-DSCSA-OUT-2026-00002.pdf OK
    File: EPCIS-DSCSA-OUT-2026-00002.xml OK
  3. Compare the Stored hash with the Hash you noted in part 1 step 2, when Odoo is available.

    Result: they are the same.

The script applies the manifest's hash_rule: the hash is $1$ followed by the SHA-256 (hex) of previous_hash and hash_payload joined together. hash_payload is the exact text Odoo hashed when it posted the document: the number, direction, company, chain position, the frozen document data and each file's SHA-256. A file can be checked without the script too: its SHA-256 (sha256sum FILE on Linux, shasum -a 256 FILE on macOS, certutil -hashfile FILE SHA256 on Windows) must equal the file's sha256 in the manifest.

Part 4: follow the chain to the document before​

  1. Get the manifest of the document at the chain position one lower, for example DSCSA/OUT/2026/00001 at position 7. In Odoo, it is the company's document posted just before: open the documents posted around the same time and read Chain Position (inbound and outbound documents share one chain). Without Odoo, look in the downloaded manifests for "chain_index": 7.

  2. Run the script on it (part 3, step 2).

    Result: its Stored hash, $1$f1c9e50b…, is the Previous hash of the document you checked first, and its Recomputed line says OK. Repeat down to the document you want to reach; the first document of a company has no previous hash.

Result: the document you downloaded is the one Odoo posted, unaltered, and it sits in the company's chain where Odoo put it. Records: nothing changes in Odoo or in the bucket; keep the files and the script's output as your evidence.

For a retention export​

A retention export has no manifest. Its two CSV files are under KEY-PREFIX/company-COMPANY-ID/exports/YEAR/monthly-YYYY-MM/ (manual-NN for an on-demand export), each with the SHA-256 recorded when it was queued.

  1. Download the file by version (part 2).

  2. Run aws s3api head-object --bucket BUCKET-NAME --key S3-KEY --version-id VERSION-ID --checksum-mode ENABLED.

    Result: Metadata shows dscsa-sha256 (hex) and ChecksumSHA256 shows the same digest in base64, as S3 checked it on upload. (Not run end to end while this page was written.)

  3. Compute the file's SHA-256 (see part 3) and compare it with dscsa-sha256, and, when Odoo is available, with Package Ledger SHA-256 or License Register SHA-256 on the export's form.

    Result: the three are the same, for example 715838b9fbafedb16cfb1a95712f9ef4f84dfe08025251305350a6ef8e5983b6 for package_ledger_2026-09-01_2026-09-28.csv on the example database.

If it doesn't work

  • Recomputed says MISMATCH: the manifest text was changed after it was written (a copy edited by hand, or a line ending changed by a text editor). Download the manifest again by its version ID and don't open it in an editor before the check. If the object from the bucket still doesn't match, keep it and tell your compliance manager: it isn't what Odoo posted.
  • A File line says MISMATCH (or not in this manifest): the file is saved under another name than its files[].name, it belongs to another document, or it was altered. Rename it, or download it again by version.
  • The previous document's hash isn't the Previous hash: you took the wrong document (another company has its own chain), or a document is missing. In Odoo, run Verify the integrity of the DSCSA documents.
  • AccessDenied from AWS: the AWS user can't read the bucket or its versions. Ask your AWS administrator for s3:GetObject and s3:GetObjectVersion.
  • The S3 Manifest Key is empty: the document isn't Archived yet. See Watch the archive queue and each record's archive state.

What each manifest field holds: The document manifest.