API reference

Synthetic accounts: password by email

A synthetic account's password is either generated and shown to the owner once, or set by whoever reads the account's inbox through a single-use setup link, so the owner never holds it. This page covers the email mode. The product side (which to pick, plus addresses, what the link does) is in Getting started.

Create an account in email mode

POST /v1/team/synthetic-accounts takes two new fields:

FieldValue
credential_modegenerated (default) or email. email needs email, one of your verified emails; without it the answer is 400 email_required.
email_tagOptional. The tag of the address, agent- followed by 10 characters of 0-9 a-h j k m n p-t v-z (for example agent-7k2m9qx4bd), so your client can show the exact address before saving. Omit it and the server picks one. A tag already in use is 409 email_taken: pick a new one and retry.

Two-factor still comes from mfa_enabled and mfa_method; in email mode the factor is set up on the link's page, never shown to you.

Create, email mode
POST /v1/team/synthetic-accounts
{
  "label": "Reconciliation bot",
  "role": "agent_full",
  "credential_mode": "email",
  "email": "ops@acme.com",
  "email_tag": "agent-7k2m9qx4bd",
  "mfa_enabled": true,
  "mfa_method": "totp"
}

In email mode the 201 answer has credentials: null and setup, the link's state, with status: "pending". If the mail could not be sent, the account still exists with status: "send_failed": send it again with email-setup. Generated mode is unchanged.

Fields on every synthetic account

The account object (GET /v1/team/synthetic-accounts, and account in every answer below) is snake_case, like the rest of the API: created_by_end_user_sub, password_rotated_at, last_seen_at, archived_at, created_at, flow_profile and so on. The old camelCase names (createdByEndUserSub, passwordRotatedAt, …) are still sent next to them with the same values, for now, and such a response has Deprecation: true; read the snake_case names. See Conventions. Two fields belong to setup by email:

FieldMeaning
credential_modegenerated or email.
setupThe setup link's state; null for generated accounts.
statuspending (sent, unused), expired (ran out or was cancelled unused; send a new one), send_failed (try again), bounced (the address bounced or is suppressed; change the email) or completed (the password is set).
sent_toThe address the link goes to: the account’s sign-in address.
lands_inThe inbox that receives it: sent_to without its agent-… tag.
sent_at, expires_at, completed_at, bounced_atISO 8601 timestamps, or null.
send_count_24hSetup emails sent for this account in the last 24 hours (at most 5).
next_send_allowed_atWhen another link may be sent; null means now.

Send, resend or move a setup link

POST /v1/team/synthetic-accounts/:display_id/email-setup mails a new link and answers 200 with {"account": …}. Any earlier unused link stops working. The body is optional: a plain resend sends none, or {}.

FieldValue
emailA verified email of yours. Omit it, or send the current base, to keep the address. Another base changes the account’s sign-in address.
email_tagAs on create: a new tag gives a new address.
mfa_methodtotp, email or none: the factor the link holder sets up. Omit it to keep the account's choice.
The account isWhat the call doesAudit reason
GeneratedSwitches it to email mode. Its password is replaced by one nobody knows, every session ends and every two-factor factor is turned off at once. It cannot sign in until the link is used.mode_change
Email, link unusedA resend. Nothing else changes.resend
Email, link usedA restart: signs it out and turns off its password and factors first, like the switch.restart
Any, with a new base or tagChanges the sign-in address (re-verified), then resends or restarts as above.email_change

A send our mail provider refuses still answers 200, with setup.status: "send_failed". Retrying within the cooldown answers 429, so a retry never sends twice. An archived account answers 409 synthetic_account_archived.

Back to a generated password

POST /v1/team/synthetic-accounts/:display_id/rotate-password on an email-mode account switches it back to credential_mode: "generated", returns the new password once and makes any unused link stop working. POST /v1/team/synthetic-accounts/:display_id/rotate-mfa is refused on an email-mode account with 409 credential_mode_email, because it would show you a TOTP secret: send a new setup link, or switch to a generated password first. Disabling MFA and archiving work in both modes; archiving revokes an unused link.

The setup link and the public routes

The link is https://portal.dispute.markets/setup-account#token=dmsu_…. The token is in the URL fragment, so it never reaches a server log or a Refererheader. It is never returned by any API: it exists only in the mail. The portal's setup page calls these two routes; your own tooling can too.

Inspect: what the link is for

POST /v1/auth/account-setup/inspect with {"token": "dmsu_…"} consumes nothing, so it is safe to call whenever the page opens. It answers 200 with label, workspace_name, sign_in_email (the identifier to sign in with), username (null when unknown), role, expires_at, mfa_method and password_policy (min_length).

Complete: set the password

POST /v1/auth/account-setup/complete with {"token": "dmsu_…", "new_password": "…"} uses the link once. It sets the password (our sign-in service applies its password policy and breach check, and ends every session), then sets up the factor the owner chose and returns its material exactly once:

200, with an authenticator app
{
  "sign_in_email": "ops+agent-7k2m9qx4bd@acme.com",
  "username": "reconciliation-bot",
  "mfa": {
    "method": "totp",
    "totp_uri": "otpauth://totp/…",
    "totp_secret": "JBSWY3DPEHPK3PXP…",
    "totp_qr_data_url": "data:image/png;base64,…",
    "recovery_codes": ["…", "…"]
  }
}

With email codes, mfa is {"method": "email", "recovery_codes": […]}; without two-factor it is null. A password that is refused is 400 password_rejected (details.reason says why) and leaves the link usable. If the password is set but the factor cannot be, the answer is 502 mfa_setup_failed: the owner sends a new link.

When our sign-in service itself fails, complete answers 503 auth_unavailable (retry after Retry-After seconds) or 502 auth_misconfigured. The link was not used either way. These routes take no bearer token, so they never answer 401 or 403.

Error codes

StatuscodeWhereMeaning and what to do
400email_invalidcreate, email-setupQuoted or non-ASCII local part, a dispute.markets domain, or a malformed email_tag. Pick another email.
400email_too_longcreate, email-setupNo room for the 17-character tag within 64 characters before the @. Use a shorter address.
400email_not_verifiedcreate, email-setupNot one of your verified emails. Add and verify it (POST /v1/me/emails) first. A resend or restart checks the account’s current inbox too: if you removed that email, send email with a verified one.
400email_requiredcreate, email-setupcredential_mode: "email" without email, or an account with no mailbox and no email.
409email_takencreate, email-setupThe address is in use. Send a new email_tag and retry.
409setup_email_suppressedcreate, email-setupMail to this address bounced before. Pick another email. Nothing was sent.
409setup_pending_limitcreate, email-setupThe workspace has 25 unused setup links. Archive or finish some first.
429setup_email_rate_limitedcreate, email-setupToo many setup emails (see the limits below). details.retry_after_s and Retry-After say when to retry.
503setup_email_unavailablecreate, email-setupSetup emails are unavailable right now. Use a generated password or retry later.
409credential_change_in_progressemail-setup, rotate-*, mfa, completeAnother change to this account’s credentials is still running (a link being used, a resend, a rotation). Nothing changed and a link was not used; retry after Retry-After seconds.
409credential_mode_emailrotate-mfaThe account’s credentials are set by email. Send a new setup link, or switch to a generated password.
404setup_link_invalidinspect, completeNo such link. Check the whole link was copied.
410setup_link_expiredinspect, complete72 hours passed. Ask the workspace owner for a new link.
410setup_link_usedinspect, completeAlready used. Sign in, or use Forgot password.
410setup_link_revokedinspect, completeA newer link was sent, or the account was archived or switched to a generated password. Use the most recent email.
400password_rejectedcompleteThe password was refused (details.reason). The link stays usable.
502mfa_setup_failedcompleteThe password is set but the two-factor factor is not. Ask the owner to send a new link.
429too_many_attemptsinspect, completeMore than 20 attempts a minute from one IP, or 10 for one link. On complete also when the IP used up its 5 password-reset codes a minute at the authentication service; the link was not used. See Retry-After.
400bad_requestemail-setupOur sign-in service refused the address change or the password reset (message says why). No link was sent.
503auth_unavailableemail-setup, completeOur sign-in service is unavailable. No link was sent, or the link was not used; retry after Retry-After seconds.
502auth_misconfiguredemail-setup, completeOur sign-in service refused a request dispute.markets made for you. No link was sent, or the link was not used. Retry later, and quote request_id if it persists.
401api_key_not_allowedcreate, email-setupCalled with an API key. Only the workspace owner, signed in, can create accounts or send links.
429rate_limitedcreate, email-setup, rotate-password, rotate-credentialsYour IP used up its 5 verification and password-reset codes a minute at the authentication service (a create or address change uses 2, a sign-out resend, mode switch or rotation 1). Nothing changed; retry after Retry-After seconds.

Limits

LimitValueAnswer
Who can sendThe workspace owner, in a session. No API key, whatever its scopes.401 api_key_not_allowed
Cooldown per accountOne send per 60 seconds429 setup_email_rate_limited
Sends per account5 per 24 hours429, as above
Sends per workspace20 per hour, 60 per 24 hours429, as above
Unused links per workspace25409 setup_pending_limit
All workspaces togetherA service-wide daily cap503 setup_email_unavailable
Link lifetime72 hours, single use, one live link per account410 setup_link_expired
Where mail goesOnly plus-addresses of your own verified emails400 email_not_verified

The account's setup.next_send_allowed_at says when the next send is allowed, so a client can show a countdown instead of hitting the 429.

Audit

Sends are recorded as synthetic_account.send_setup_email (also with outcome: failed when a limit or the suppression check stopped the send), a used link as synthetic_account.complete_setup (the actor is the synthetic account) and a bounce found after the send as synthetic_account.setup_email_bounced. Neither the link nor the address is ever recorded. See Audit events for every field, and Testing a setup-link flow for how to test it.