diff --git a/gui/src/components/ConfirmDialog.js b/gui/src/components/ConfirmDialog.js
index a54d60b8843c387549864f11db43df6eb884c7ee..1591239c70dfe9f293d0adb74b5448c5e8b028ab 100644
--- a/gui/src/components/ConfirmDialog.js
+++ b/gui/src/components/ConfirmDialog.js
@@ -6,39 +6,49 @@ import DialogActions from '@material-ui/core/DialogActions'
 import DialogContent from '@material-ui/core/DialogContent'
 import DialogContentText from '@material-ui/core/DialogContentText'
 import DialogTitle from '@material-ui/core/DialogTitle'
+import { FormGroup, Checkbox, FormLabel } from '@material-ui/core'
 
 class ConfirmDialog extends React.Component {
   static propTypes = {
-    onOk: PropTypes.func.isRequired,
+    onPublish: PropTypes.func.isRequired,
     onClose: PropTypes.func.isRequired,
-    open: PropTypes.bool,
-    title: PropTypes.string,
-    children: PropTypes.any
+    open: PropTypes.bool.isRequired
   }
 
-  render() {
-    const { children, title } = this.props
+  state = {
+    withEmbargo: false
+  }
 
+  render() {
+    const { onPublish, onClose, open } = this.props
+    const { withEmbargo } = this.state
     return (
       <div>
         <Dialog
-          open={this.props.open}
-          onClose={this.handleClose}
-          aria-labelledby="alert-dialog-title"
-          aria-describedby="alert-dialog-description"
+          open={open}
+          onClose={onClose}
         >
-          <DialogTitle id="alert-dialog-title">{title || 'Confirm'}</DialogTitle>
+          <DialogTitle>Publish data</DialogTitle>
           <DialogContent>
-            <DialogContentText id="alert-dialog-description">
-              {children}
+            <DialogContentText>
+              If you agree the selected uploads will move out of your private staging
+               area into the public nomad.
             </DialogContentText>
+
+            <FormGroup row style={{alignItems: 'center'}}>
+              <Checkbox
+                checked={!withEmbargo}
+                onChange={() => this.setState({withEmbargo: !withEmbargo})}
+              />
+              <FormLabel>publish without embargo</FormLabel>
+            </FormGroup>
           </DialogContent>
           <DialogActions>
-            <Button onClick={this.props.onClose} color="primary">
-              Disagree
+            <Button onClick={onClose} color="primary">
+              Cancel
             </Button>
-            <Button onClick={this.props.onOk} color="primary" autoFocus>
-              Agree
+            <Button onClick={() => onPublish(withEmbargo)} color="primary" autoFocus>
+              {withEmbargo ? 'Publish with embargo' : 'Publish'}
             </Button>
           </DialogActions>
         </Dialog>
diff --git a/gui/src/components/Upload.js b/gui/src/components/Upload.js
index 033ee26c8e81ded5b91be391f7f3583836a1b1ee..fedca6333530c9c5fece43b413c599b8c08c1415 100644
--- a/gui/src/components/Upload.js
+++ b/gui/src/components/Upload.js
@@ -190,7 +190,7 @@ class Upload extends React.Component {
     const { calcs, tasks, current_task, tasks_running, tasks_status, process_running, current_process, errors } = upload
 
     // map tasks [ uploading, extracting, parse_all, cleanup ] to steps
-    const steps = [ 'upload', 'process', 'commit' ]
+    const steps = [ 'upload', 'process', 'publish' ]
     let step = null
     const task_index = tasks.indexOf(current_task)
     if (task_index === 0) {
@@ -198,7 +198,7 @@ class Upload extends React.Component {
     } else if (task_index > 0 && tasks_running) {
       step = 'process'
     } else {
-      step = 'commit'
+      step = 'publish'
     }
     const stepIndex = steps.indexOf(step)
 
@@ -278,11 +278,11 @@ class Upload extends React.Component {
           )
         }
       },
-      commit: (props) => {
+      publish: (props) => {
         props.children = 'inspect'
 
         if (process_running) {
-          if (current_process === 'commit_upload') {
+          if (current_process === 'publish_upload') {
             props.children = 'approved'
             props.optional = <Typography variant="caption">moving data ...</Typography>
           } else if (current_process === 'delete_upload') {
@@ -290,7 +290,7 @@ class Upload extends React.Component {
             props.optional = <Typography variant="caption">deleting data ...</Typography>
           }
         } else {
-          props.optional = <Typography variant="caption">commit or delete</Typography>
+          props.optional = <Typography variant="caption">publish or delete</Typography>
         }
       }
     }
diff --git a/gui/src/components/Uploads.js b/gui/src/components/Uploads.js
index d36a3697c47af051cd70c615ecde563dd8abe24b..a3ebc24d6727b75f5c234eff01a495d95264b6e3 100644
--- a/gui/src/components/Uploads.js
+++ b/gui/src/components/Uploads.js
@@ -1,9 +1,10 @@
 import React from 'react'
-import PropTypes from 'prop-types'
+import PropTypes, { instanceOf } from 'prop-types'
 import Markdown from './Markdown'
 import { withStyles, Paper, IconButton, FormGroup, Checkbox, FormControlLabel, FormLabel,
   LinearProgress,
-  Typography} from '@material-ui/core'
+  Typography,
+  Tooltip} from '@material-ui/core'
 import UploadIcon from '@material-ui/icons/CloudUpload'
 import Dropzone from 'react-dropzone'
 import Upload from './Upload'
@@ -12,14 +13,16 @@ import DeleteIcon from '@material-ui/icons/Delete'
 import ReloadIcon from '@material-ui/icons/Cached'
 import CheckIcon from '@material-ui/icons/Check'
 import ConfirmDialog from './ConfirmDialog'
-import { Help } from './help'
+import { Help, Agree } from './help'
 import { withApi } from './api'
+import { withCookies, Cookies } from 'react-cookie'
 
 class Uploads extends React.Component {
   static propTypes = {
     classes: PropTypes.object.isRequired,
     api: PropTypes.object.isRequired,
-    raiseError: PropTypes.func.isRequired
+    raiseError: PropTypes.func.isRequired,
+    cookies: instanceOf(Cookies).isRequired
   }
 
   static styles = theme => ({
@@ -72,7 +75,7 @@ class Uploads extends React.Component {
     uploadCommand: 'loading ...',
     selectedUploads: [],
     loading: true,
-    showAccept: false
+    showPublish: false
   }
 
   componentDidMount() {
@@ -107,15 +110,16 @@ class Uploads extends React.Component {
       })
   }
 
-  onAcceptClicked() {
-    this.setState({showAccept: true})
+  onPublishClicked() {
+    this.setState({showPublish: true})
   }
 
-  handleAccept() {
+  onPublish(withEmbargo) {
     this.setState({loading: true})
-    Promise.all(this.state.selectedUploads.map(upload => this.props.api.commitUpload(upload.upload_id)))
+    Promise.all(this.state.selectedUploads
+      .map(upload => this.props.api.publishUpload(upload.upload_id, withEmbargo)))
       .then(() => {
-        this.setState({showAccept: false})
+        this.setState({showPublish: false})
         return this.update()
       })
       .catch(error => {
@@ -163,7 +167,7 @@ class Uploads extends React.Component {
 
   renderUploads() {
     const { classes } = this.props
-    const { selectedUploads } = this.state
+    const { selectedUploads, showPublish } = this.state
     const uploads = this.state.uploads || []
 
     return (<div>
@@ -175,23 +179,36 @@ class Uploads extends React.Component {
               onChange={(_, checked) => this.onSelectionAllChanged(checked)}
             />
           )} />
-          <IconButton onClick={() => this.update()}><ReloadIcon /></IconButton>
+          <Tooltip title="reload uploads" >
+            <IconButton onClick={() => this.update()}><ReloadIcon /></IconButton>
+          </Tooltip>
           <FormLabel classes={{root: classes.selectLabel}}>
             {`selected uploads ${selectedUploads.length}/${uploads.length}`}
           </FormLabel>
-          <IconButton
-            disabled={selectedUploads.length === 0}
-            onClick={this.onDeleteClicked.bind(this)}
-          >
-            <DeleteIcon />
-          </IconButton>
+          <Tooltip title="delete selected uploads" >
+            <div>
+              <IconButton
+                disabled={selectedUploads.length === 0}
+                onClick={this.onDeleteClicked.bind(this)}
+              >
+                <DeleteIcon />
+              </IconButton>
+            </div>
+          </Tooltip>
+
+          <Tooltip title="publish selected uploads" >
+            <div>
+              <IconButton disabled={selectedUploads.length === 0} onClick={() => this.onPublishClicked()}>
+                <CheckIcon />
+              </IconButton>
+            </div>
+          </Tooltip>
 
-          <IconButton disabled={selectedUploads.length === 0} onClick={this.onAcceptClicked.bind(this)}>
-            <CheckIcon />
-          </IconButton>
-          <ConfirmDialog open={this.state.showAccept} onClose={() => this.setState({showAccept: false})} onOk={this.handleAccept.bind(this)}>
-              If you agree the selected uploads will move out of your private staging area into the public nomad.
-          </ConfirmDialog>
+          <ConfirmDialog
+            open={showPublish}
+            onClose={() => this.setState({showPublish: false})}
+            onPublish={(withEmbargo) => this.onPublish(withEmbargo)}
+          />
 
         </FormGroup>
       </div>
@@ -200,11 +217,15 @@ class Uploads extends React.Component {
           ? (
             <div>
               <Help cookie="uploadList">{`
-                These are all your existing not commiting uploads. You can see how processing
-                progresses and review your uploads before commiting them to the *nomad repository*.
+                These are all your uploads in the *staging area*. You can see the
+                progress on data progresses and review your uploads before publishing
+                them to the *nomad repository*.
 
-                Select uploads to delete or commit them. Click on uploads to see individual
+                Select uploads to delete or publish them. Click on uploads to see individual
                 calculations. Click on calculations to see more details on each calculation.
+
+                When you select and click publish, you will be ask if you want to publish
+                with or without the optional *embargo period*.
               `}</Help>
               {
                 this.sortedUploads().map(upload => (
@@ -224,46 +245,69 @@ class Uploads extends React.Component {
     const { classes } = this.props
     const { uploadCommand } = this.state
 
+    const agreement = `
+      By uploading and downloading data, you agree to the
+      [terms of use](https://www.nomad-coe.eu/the-project/nomad-repository/nomad-repository-terms).
+
+      Note that uploaded files become downloadable. Uploaded data is licensed under the
+      Creative Commons Attribution license ([CC BY 3.0](https://creativecommons.org/licenses/by/3.0/)).
+      You can put an *embargo* on uploaded data. The *embargo period* lasts up to 36 month.
+      If you do not decide on an *embargo* after upload, data will be made public after 48h
+      automatically.
+    `
+
     return (
       <div className={classes.root}>
         <Typography variant="h4">Upload your own data</Typography>
-        <Help cookie="uploadHelp" component={Markdown}>{`
-          You can upload your own data. Have your code output ready in a popular archive
-          format (e.g. \`*.zip\` or \`*.tar.gz\`).  Your upload can
-          comprise the output of multiple runs, even of different codes. Don't worry, nomad
-          will find it, just drop it below:
-        `}</Help>
+        <Agree message={agreement} cookie="agreedToUploadTerms">
+          <Help cookie="uploadHelp" component={Markdown}>{`
+            To upload your own data, please put all relevant files in a
+            \`*.zip\` or \`*.tar.gz\` archive. We encourage you to add all code input and
+            output files, as well as any other auxiliary files that you might have created.
+            You can put data from multiple calculations, using your preferred directory
+            structure, into your archives. Drop your archive file(s) below.
+
+            Uploaded data will not be public immediately. This is called the *staging area*.
+            After uploading and processing, you can decide if you want to make the data public,
+            delete it again, or put an *embargo* on it.
+
+            The *embargo* allows you to shared it with selected users, create a DOI
+            for your data, and later publish the data. The *embargo* might last up to
+            36 month before it becomes public automatically. During an *embargo*
+            some meta-data will be available.
+          `}</Help>
 
-        <Paper className={classes.dropzoneContainer}>
-          <Dropzone
-            accept={['application/zip', 'application/gzip', 'application/bz2']}
-            className={classes.dropzone}
-            activeClassName={classes.dropzoneAccept}
-            rejectClassName={classes.dropzoneReject}
-            onDrop={this.onDrop.bind(this)}
-          >
-            <p>drop files here</p>
-            <UploadIcon style={{fontSize: 36}}/>
-          </Dropzone>
-        </Paper>
+          <Paper className={classes.dropzoneContainer}>
+            <Dropzone
+              accept={['application/zip', 'application/gzip', 'application/bz2']}
+              className={classes.dropzone}
+              activeClassName={classes.dropzoneAccept}
+              rejectClassName={classes.dropzoneReject}
+              onDrop={this.onDrop.bind(this)}
+            >
+              <p>drop files here</p>
+              <UploadIcon style={{fontSize: 36}}/>
+            </Dropzone>
+          </Paper>
 
-        <Help cookie="uploadCommandHelp">{`
-          Alternatively, you can upload files via the following shell command.
-          Replace \`<local_file>\` with your file. After executing the command,
-          return here and reload.
-        `}</Help>
+          <Help cookie="uploadCommandHelp">{`
+            Alternatively, you can upload files via the following shell command.
+            Replace \`<local_file>\` with your archive file. After executing the command,
+            return here and reload (e.g. press the reload button below).
+          `}</Help>
 
-        <Markdown>{`
-          \`\`\`
-            ${uploadCommand}
-          \`\`\`
-        `}</Markdown>
+          <Markdown>{`
+            \`\`\`
+              ${uploadCommand}
+            \`\`\`
+          `}</Markdown>
 
-        {this.renderUploads()}
-        {this.state.loading ? <LinearProgress/> : ''}
+          {this.renderUploads()}
+          {this.state.loading ? <LinearProgress/> : ''}
+        </Agree>
       </div>
     )
   }
 }
 
-export default compose(withApi(true), withStyles(Uploads.styles))(Uploads)
+export default compose(withApi(true), withCookies, withStyles(Uploads.styles))(Uploads)
diff --git a/gui/src/components/api.js b/gui/src/components/api.js
index 0b909f064e7bed3bc58c7ac1411d0452ecc3f583..47445af4d9b9e1fc06b54f0dec6ab72760bcb518 100644
--- a/gui/src/components/api.js
+++ b/gui/src/components/api.js
@@ -215,12 +215,15 @@ class Api {
       .then(response => response.body)
   }
 
-  async commitUpload(uploadId) {
+  async publishUpload(uploadId, withEmbargo) {
     const client = await this.swaggerPromise
-    return client.apis.uploads.exec_upload_command({
+    return client.apis.uploads.exec_upload_operation({
       upload_id: uploadId,
       payload: {
-        command: 'commit'
+        operation: 'publish',
+        metadata: {
+          with_embargo: withEmbargo
+        }
       }
     })
       .catch(this.handleApiError)
diff --git a/gui/src/components/help.js b/gui/src/components/help.js
index 69220a9d80c79efbe869895c95ae524a3732ad75..0e27f234c7c22d912f8446c84047d17305ea747c 100644
--- a/gui/src/components/help.js
+++ b/gui/src/components/help.js
@@ -1,8 +1,9 @@
 import React from 'react'
-import { withStyles, Button } from '@material-ui/core'
+import { withStyles, Button, Collapse, Fade } from '@material-ui/core'
 import Markdown from './Markdown'
 import PropTypes, { instanceOf } from 'prop-types'
 import { Cookies, withCookies } from 'react-cookie'
+import classNames from 'classnames'
 
 export const HelpContext = React.createContext()
 
@@ -51,17 +52,98 @@ class HelpProviderComponent extends React.Component {
   }
 }
 
-class HelpComponent extends React.Component {
+export class Help extends React.Component {
+  static propTypes = {
+    children: PropTypes.any,
+    cookie: PropTypes.string.isRequired
+  }
+
+  render() {
+    const { children, cookie } = this.props
+
+    return (
+      <HelpContext.Consumer>{
+        help => (
+          <Collapse in={help.isOpen(cookie)}>
+            <GotIt onGotIt={() => help.gotIt(cookie)}>
+              {children}
+            </GotIt>
+          </Collapse>
+        )
+      }</HelpContext.Consumer>
+    )
+  }
+}
+
+class AgreeComponent extends React.Component {
+  static propTypes = {
+    children: PropTypes.oneOfType([
+      PropTypes.node, PropTypes.arrayOf(PropTypes.node)
+    ]).isRequired,
+    message: PropTypes.string.isRequired,
+    cookie: PropTypes.string.isRequired,
+    cookies: instanceOf(Cookies).isRequired
+  }
+
+  state = {
+    agreed: this.props.cookies.get(this.props.cookie)
+  }
+
+  onAgreeClicked() {
+    this.props.cookies.set(this.props.cookie, true)
+    this.setState({agreed: true})
+  }
+
+  render() {
+    const { children, message } = this.props
+    const { agreed } = this.state
+
+    return (
+      <div>
+        <Collapse in={!agreed}>
+          <GotIt onGotIt={() => this.onAgreeClicked()} color="error">
+            {message}
+          </GotIt>
+        </Collapse>
+        <Fade in={!!agreed}>
+          <div>
+            {children}
+          </div>
+        </Fade>
+      </div>
+    )
+  }
+}
+
+export const Agree = withCookies(AgreeComponent)
+
+class GotItUnstyled extends React.Component {
+  static propTypes = {
+    classes: PropTypes.object.isRequired,
+    onGotIt: PropTypes.func.isRequired,
+    children: PropTypes.node.isRequired,
+    color: PropTypes.oneOf(['primary', 'error']).isRequired
+  }
+
+  static defaultProps = {
+    color: 'primary'
+  }
+
   static styles = theme => ({
     root: {
       marginTop: theme.spacing.unit * 2,
       marginBottom: theme.spacing.unit * 2,
       borderRadius: theme.spacing.unit * 0.5,
-      border: `1px solid ${theme.palette.primary.main}`,
       display: 'flex',
       flexDirection: 'row',
       alignItems: 'center'
     },
+    rootPrimary: {
+      border: `1px solid ${theme.palette.primary.main}`
+    },
+    rootError: {
+      border: `1px solid ${theme.palette.error.main}`
+    },
     content: {
       paddingLeft: theme.spacing.unit * 2,
       flex: '1 1 auto'
@@ -72,34 +154,27 @@ class HelpComponent extends React.Component {
     }
   })
 
-  static propTypes = {
-    classes: PropTypes.object.isRequired,
-    children: PropTypes.any,
-    cookie: PropTypes.string.isRequired
-  }
-
   render() {
-    const { classes, children, cookie } = this.props
-
+    const { classes, children, onGotIt, color } = this.props
+    const rootClassName = classNames(classes.root, {
+      [classes.rootPrimary]: color === 'primary',
+      [classes.rootError]: color === 'error'
+    })
     return (
-      <HelpContext.Consumer>{
-        help => (
-          help.isOpen(cookie)
-            ? <div className={classes.root}>
-              <div className={classes.content}>
-                <Markdown>
-                  {children}
-                </Markdown>
-              </div>
-              <div className={classes.actions}>
-                <Button color="primary" onClick={() => help.gotIt(cookie)}>Got it</Button>
-              </div>
-            </div> : ''
-        )
-      }</HelpContext.Consumer>
+      <div className={rootClassName}>
+        <div className={classes.content}>
+          <Markdown>
+            {children}
+          </Markdown>
+        </div>
+        <div className={classes.actions}>
+          <Button color="primary" onClick={onGotIt}>Got it</Button>
+        </div>
+      </div>
     )
   }
 }
 
+const GotIt = withStyles(GotItUnstyled.styles)(GotItUnstyled)
+
 export const HelpProvider = withCookies(HelpProviderComponent)
-export const Help = withStyles(HelpComponent.styles)(HelpComponent)
diff --git a/nomad/api/upload.py b/nomad/api/upload.py
index 17ab525ac4a66c7deb77d57e1fee85d08be55715..241038773bdf5032784f881d550e1a16531a6f0b 100644
--- a/nomad/api/upload.py
+++ b/nomad/api/upload.py
@@ -103,8 +103,8 @@ upload_with_calcs_model = api.inherit('UploadWithPaginatedCalculations', upload_
     }))
 })
 
-upload_command_model = api.model('UploadCommand', {
-    'command': fields.String(description='Currently commit is the only command.'),
+upload_operation_model = api.model('UploadOperation', {
+    'operation': fields.String(description='Currently publish is the only operation.'),
     'metadata': fields.Nested(model=upload_metadata_model, description='Additional upload and calculation meta data. Will replace previously given metadata.')
 })
 
@@ -300,20 +300,20 @@ class UploadResource(Resource):
 
         return upload, 200
 
-    @api.doc('exec_upload_command')
+    @api.doc('exec_upload_operation')
     @api.response(404, 'Upload does not exist or not in staging')
     @api.response(400, 'Operation is not supported or the upload is still/already processed')
-    @api.response(401, 'If the command is not allowed for the current user')
-    @api.marshal_with(upload_model, skip_none=True, code=200, description='Upload commited successfully')
-    @api.expect(upload_command_model)
+    @api.response(401, 'If the operation is not allowed for the current user')
+    @api.marshal_with(upload_model, skip_none=True, code=200, description='Upload published successfully')
+    @api.expect(upload_operation_model)
     @login_really_required
     def post(self, upload_id):
         """
-        Execute an upload command. Available operations: ``commit``
+        Execute an upload operation. Available operations: ``publish``
 
         Unstage accepts further meta data that allows to provide coauthors, comments,
         external references, etc. See the model for details. The fields that start with
-        ``_underscore`` are only available for users with administrative priviledges.
+        ``_underscore`` are only available for users with administrative privileges.
 
         Unstage changes the visibility of the upload. Clients can specify the visibility
         via meta data.
@@ -330,7 +330,7 @@ class UploadResource(Resource):
         if json_data is None:
             json_data = {}
 
-        command = json_data.get('command')
+        operation = json_data.get('operation')
 
         metadata = json_data.get('metadata', {})
         for key in metadata:
@@ -339,20 +339,20 @@ class UploadResource(Resource):
                     abort(401, message='Only admin users can use _metadata_keys.')
                 break
 
-        if command == 'commit':
+        if operation == 'publish':
             if upload.tasks_running:
                 abort(400, message='The upload is not processed yet')
             if upload.tasks_status == FAILURE:
-                abort(400, message='Cannot commit an upload that failed processing')
+                abort(400, message='Cannot publish an upload that failed processing')
             try:
                 upload.metadata = metadata
-                upload.commit_upload()
+                upload.publish_upload()
             except ProcessAlreadyRunning:
                 abort(400, message='The upload is still/already processed')
 
             return upload, 200
 
-        abort(400, message='Unsuported command %s.' % command)
+        abort(400, message='Unsuported operation %s.' % operation)
 
 
 upload_command_model = api.model('UploadCommand', {
diff --git a/nomad/client/upload.py b/nomad/client/upload.py
index 815076e7f6d5b9e649ac03719ba723cb16621951..84d13056def6ddfeab907c673ca6f96277800916 100644
--- a/nomad/client/upload.py
+++ b/nomad/client/upload.py
@@ -23,7 +23,7 @@ from nomad.processing import FAILURE, SUCCESS
 from .main import cli, create_client
 
 
-def upload_file(file_path: str, name: str = None, offline: bool = False, commit: bool = False, client=None):
+def upload_file(file_path: str, name: str = None, offline: bool = False, publish: bool = False, client=None):
     """
     Upload a file to nomad.
 
@@ -31,7 +31,7 @@ def upload_file(file_path: str, name: str = None, offline: bool = False, commit:
         file_path: path to the file, absolute or relative to call directory
         name: optional name, default is the file_path's basename
         offline: allows to process data without upload, requires client to be run on the server
-        commit: automatically commit after successful processing
+        publish: automatically publish after successful processing
 
     Returns: The upload_id
     """
@@ -66,8 +66,8 @@ def upload_file(file_path: str, name: str = None, offline: bool = False, commit:
         click.echo('There have been errors:')
         for error in upload.errors:
             click.echo('    %s' % error)
-    elif commit:
-        client.uploads.exec_upload_command(upload_id=upload.upload_id, command='commit').reponse()
+    elif publish:
+        client.uploads.exec_upload_operation(upload_id=upload.upload_id, payload=dict(operation='publish')).response()
 
     return upload.upload_id
 
@@ -84,9 +84,9 @@ def upload_file(file_path: str, name: str = None, offline: bool = False, commit:
     help='Upload files "offline": files will not be uploaded, but processed were they are. '
     'Only works when run on the nomad host.')
 @click.option(
-    '--commit', is_flag=True, default=False,
+    '--publish', is_flag=True, default=False,
     help='Automatically move upload out of the staging area after successful processing')
-def upload(path, name: str, offline: bool, commit: bool):
+def upload(path, name: str, offline: bool, publish: bool):
     utils.configure_logging()
     paths = path
     click.echo('uploading files from %s paths' % len(paths))
@@ -94,7 +94,7 @@ def upload(path, name: str, offline: bool, commit: bool):
         click.echo('uploading %s' % path)
         if os.path.isfile(path):
             name = name if name is not None else os.path.basename(path)
-            upload_file(path, name, offline, commit)
+            upload_file(path, name, offline, publish)
 
         elif os.path.isdir(path):
             for (dirpath, _, filenames) in os.walk(path):
@@ -102,7 +102,7 @@ def upload(path, name: str, offline: bool, commit: bool):
                     if filename.endswith('.zip'):
                         file_path = os.path.abspath(os.path.join(dirpath, filename))
                         name = os.path.basename(file_path)
-                        upload_file(file_path, name, offline, commit)
+                        upload_file(file_path, name, offline, publish)
 
         else:
             click.echo('Unknown path type %s.' % path)
diff --git a/nomad/coe_repo/upload.py b/nomad/coe_repo/upload.py
index 1d4a03787ac32bee9ff20ea4b832b001843f1f84..5ff22258a2a20aa6e9424ebcd23369ef264af04d 100644
--- a/nomad/coe_repo/upload.py
+++ b/nomad/coe_repo/upload.py
@@ -130,6 +130,8 @@ class Upload(Base, datamodel.Upload):  # type: ignore
                 meta data) that should be added to upload and calculations.
         """
         upload_metadata = UploadMetaData(metadata)
+        assert upload.uploader is not None
+
         repo_db = infrastructure.repository_db
         repo_db.begin()
 
@@ -222,8 +224,13 @@ class Upload(Base, datamodel.Upload):  # type: ignore
         coe_calc.set_value(base.topic_basis_set_type, calc.basis_set_type)
 
         # user relations
-        owner_user_id = calc_metadata.get('_uploader', int(self.user_id))
-        coe_calc.owners.append(repo_db.query(User).get(owner_user_id))
+        owner_user_id = calc_metadata.get('_uploader', None)
+        if owner_user_id is not None:
+            uploader = repo_db.query(User).get(owner_user_id)
+        else:
+            uploader = self.user
+
+        coe_calc.owners.append(uploader)
 
         for coauthor_id in calc_metadata.get('coauthors', []):
             coe_calc.coauthors.append(repo_db.query(User).get(coauthor_id))
diff --git a/nomad/infrastructure.py b/nomad/infrastructure.py
index a8498bf2349a247827afc6a47f2e4e3c8e1d0d15..be3ef673158e1dbb5a78ddc7667e59515bcb15c8 100644
--- a/nomad/infrastructure.py
+++ b/nomad/infrastructure.py
@@ -56,7 +56,7 @@ def setup():
     setup_logging()
     setup_mongo()
     setup_elastic()
-    setup_repository_db()
+    setup_repository_db(readonly=False)
 
 
 def setup_logging():
diff --git a/nomad/migration.py b/nomad/migration.py
index 4ef5cc08bd92fca5c7f05302aea810aa4cd83bbc..83c99208286daa3bf89235580acbe493a9ba7b7c 100644
--- a/nomad/migration.py
+++ b/nomad/migration.py
@@ -373,7 +373,7 @@ class NomadCOEMigration:
                         'no match or processed calc for source calc',
                         mainfile=source_calc.mainfile)
 
-            # commit upload
+            # publish upload
             admin_keys = ['upload_time, uploader, pid']
 
             def transform(calcWithMetadata):
@@ -394,9 +394,9 @@ class NomadCOEMigration:
                 if calc.__migrated]
 
             if report.total_calcs > report.failed_calcs:
-                upload = self.client.uploads.exec_upload_command(
+                upload = self.client.uploads.exec_upload_operation(
                     upload_id=upload.upload_id,
-                    payload=dict(command='commit', metadata=upload_metadata)
+                    payload=dict(operation='publish', metadata=upload_metadata)
                 ).response().result
 
                 while upload.process_running:
@@ -405,7 +405,7 @@ class NomadCOEMigration:
                             upload_id=upload.upload_id).response().result
                         time.sleep(0.1)
                     except HTTPNotFound:
-                        # the proc upload will be deleted by the commit command
+                        # the proc upload will be deleted by the publish operation
                         break
 
             # report
diff --git a/nomad/processing/data.py b/nomad/processing/data.py
index 9ddd11b05e431c0e3de9f285fb6cf947b366ddd8..ed89f436c49125f4bcd9e850e4df5d5ab28a0e4e 100644
--- a/nomad/processing/data.py
+++ b/nomad/processing/data.py
@@ -358,7 +358,7 @@ class Upload(Chord, datamodel.Upload):
         return True  # do not save the process status on the delete upload
 
     @process
-    def commit_upload(self):
+    def publish_upload(self):
         """
         Moves the upload out of staging to add it to the coe repository. It will
         pack the staging upload files in to public upload files, add entries to the
@@ -367,19 +367,19 @@ class Upload(Chord, datamodel.Upload):
         """
         logger = self.get_logger()
 
-        with utils.lnr(logger, 'commit failed'):
+        with utils.lnr(logger, 'publish failed'):
             with utils.timer(
-                    logger, 'upload added to repository', step='commit',
+                    logger, 'upload added to repository', step='publish',
                     upload_size=self.upload_files.size):
                 coe_repo.Upload.add(self, self.metadata)
 
             with utils.timer(
-                    logger, 'staged upload files packed', step='commit',
+                    logger, 'staged upload files packed', step='publish',
                     upload_size=self.upload_files.size):
                 self.upload_files.pack()
 
             with utils.timer(
-                    logger, 'staged upload deleted', step='commit',
+                    logger, 'staged upload deleted', step='publish',
                     upload_size=self.upload_files.size):
                 self.upload_files.delete()
                 self.delete()
diff --git a/tests/test_api.py b/tests/test_api.py
index fc0a58d9a9a4eba8ef1fb37328542ea8d565ff2d..b593e6797c44063946fbe9e5691659614c77e7e8 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -236,18 +236,18 @@ class TestUploads:
         rv = client.post(
             '/uploads/%s' % upload_id,
             headers=test_user_auth,
-            data=json.dumps(dict(command='commit', metadata=metadata)),
+            data=json.dumps(dict(operation='publish', metadata=metadata)),
             content_type='application/json')
         assert rv.status_code == 200
         upload = self.assert_upload(rv.data)
-        assert upload['current_process'] == 'commit_upload'
+        assert upload['current_process'] == 'publish_upload'
         assert upload['process_running']
 
         self.assert_upload_does_not_exist(client, upload_id, test_user_auth)
         assert_coe_upload(upload_id, empty=empty_upload, metadata=metadata)
 
     def assert_upload_does_not_exist(self, client, upload_id: str, test_user_auth):
-        # poll until commit/delete completed
+        # poll until publish/delete completed
         while True:
             time.sleep(0.1)
             rv = client.get('/uploads/%s' % upload_id, headers=test_user_auth)
@@ -378,7 +378,7 @@ class TestUploads:
         rv = client.post(
             '/uploads/%s' % upload['upload_id'],
             headers=test_user_auth,
-            data=json.dumps(dict(command='commit', metadata=dict(_pid=256))),
+            data=json.dumps(dict(operation='publish', metadata=dict(_pid=256))),
             content_type='application/json')
         assert rv.status_code == 401
 
@@ -390,7 +390,7 @@ class TestUploads:
     #     rv = client.post(
     #         '/uploads/%s' % upload['upload_id'],
     #         headers=test_user_auth,
-    #         data=json.dumps(dict(command='commit', metadata=dict(doesnotexist='hi'))),
+    #         data=json.dumps(dict(operation='publish', metadata=dict(doesnotexist='hi'))),
     #         content_type='application/json')
     #     assert rv.status_code == 400