Authentication for backend integrations

This page is for teams building a non-interactive client; a server, scheduled job, queue worker, or middleware layer that calls the Deal Engine GraphQL API with no human present to type credentials.

It does not repeat the login and refresh mechanics. Those are documented in Authentication and Authentication & authorization. Read one of those first; this page covers only what changes when the caller is a machine.

It is written to be handed to a security reviewer. Where the currently published behavior is not documented, this page says so instead of guessing.


1. What credential types exist today for non-interactive clients

Only one: a user account's email and password, exchanged for tokens through the login mutation.

There is no API key, no service-account credential, no OAuth 2.0 client_credentials grant, and no mutual-TLS client certificate documented anywhere in this documentation set for calling the GraphQL API. Every authentication path we publish (for browsers, for scripts, and for servers alike) begins with login(email, password).

The practical consequence, stated plainly: a server-to-server integration must store a user account's password and re-authenticate with it. Because the refresh token lives roughly 12 hours (see Token lifetimes), that password cannot be used once at setup time and then discarded; the process holding it must be able to reach it again, at minimum twice a day, for the life of the integration.

This is confirmed, not a gap in the documentation: no machine credential exists, and none is on the roadmap. There is no API key, service-account credential, client_credentials grant, or mTLS option for calling the GraphQL API today, and Deal Engine has confirmed there is no plan to add one. The login(email, password) pattern described in this section is the supported integration pattern for a non-interactive client; build against it, and treat the rest of this page as guidance for containing that fact, not as a stopgap ahead of a future machine credential.

Deal Engine's self-service password reset mutations (requestPasswordReset, resetPassword) exist in the schema and can be used to rotate a robot account's password without a support ticket; see §2.4 and §3 for how that fits into a rotation and incident plan.

Things that look like an alternative but are not

MechanismWhy it does not apply here
Webhook API Key authentication (Webhooks)Runs in the opposite direction. That key authenticates Deal Engine's requests to your endpoint. It grants no access to the Deal Engine API.
Webhook HMAC signatureSame direction problem; it verifies deliveries you receive, not calls you make.
Microsoft Entra SSOAn interactive browser sign-in for humans in the OnePoint interface. It is not a machine-to-machine grant, and this documentation does not describe a token obtained through it being usable by a headless client.
The Robot Account roleA permission set, not a credential type. A Robot Account still signs in with an email and password. See section 2.1.

Because the credential is a password, everything in the rest of this page is about containing that fact, not about working around it.


2. The interim pattern

This is what is derivable from the currently documented behavior. It is a containment pattern, not a substitute for a purpose-built machine credential.

2.1 Use a dedicated non-human account

Create a separate user account that exists only for the integration. Do not reuse a named employee's login.

  • Assign the Robot Account role, which exists for exactly this case: "This user can only use the API (server for making requests without the interface) and read requests." See Type of roles.
  • Give the account an address your team controls and can monitor (for example an alias that reaches a shared inbox, not a personal mailbox), because account emails are sent to it.
  • Accounts are created in Admin → User Management by someone holding Admin.enable, Users.read, and Users.create. See Managing users.

Two constraints to plan around before you pick this role:

The stock Robot Account role is described as read-only. It "cannot create or update quotes, access admin accounts, or process refunds" (Type of roles). Roles are customizable: an administrator can define a new role and assign permissions, and permissions not offered in the role editor require a Zendesk ticket to our backend team (Role settings).

When you raise a ticket for a write-capable role, name the capability you need; for example quote creation or ticket processing (fulfillment). Deal Engine will map that to the exact permission to grant.

🚧

Do not treat the role description as an enforced deny-list. Backend permission gating covers a specific set of operations; a role whose OnePoint description sounds restrictive may still reach operations outside that description. Grant your integration account the narrowest role you can, but place your own controls in front of money-moving calls rather than relying on the role to block them, and verify the boundary in Sandbox for your own account.

Account creation sends a password-change email. "Once you create the user, they will receive an email from Deal Engine to change their password" (Managing users). Whether that step is optional or must be completed before the account can authenticate is not documented, and it determines whether provisioning a robot account requires a human to open a mailbox.

2.2 Keep the credential in a secret manager, never in source

The password and any long-lived material must live in a managed secret store (AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault, or your platform's equivalent) and be injected into the process at runtime.

Concretely, that means:

  • No credentials in source control, container images, CI job definitions, or Helm values.
  • No credentials in .graphql files, Postman collections, or saved Apollo Sandbox tabs.
  • No credentials in log output. Log the account identity if you need traceability, never the secret, and never the token.
  • Tokens are bearer credentials too. Hold access and refresh tokens in process memory. If you must persist them (for example to share them across workers) put them in the same secret store as the password, not on disk or in an unencrypted cache.

This is the standard we already apply to webhook secrets, which are "automatically encrypted and stored in AWS Secrets Manager" and referenced by ARN rather than by value (Webhook API reference). Apply the same handling to API login credentials, which the rest of these docs currently do not say explicitly.

2.3 Never inline the password in a mutation body

If you find an example anywhere that writes credentials directly into the mutation body, treat it as an illustration of the request shape, not as a pattern to copy. A literal password inside an operation string ends up in version control, in APM traces, and in GraphQL query logs.

Do not do this:

# ANTIPATTERN: credentials inlined into the operation
mutation Login {
    login(email: "[email protected]", password: "hunter2") {
        token
    }
}

Do this instead; the operation carries variable references, and the values arrive separately from your secret store:

mutation Login($email: String!, $password: String!) {
    login(email: $email, password: $password) {
        token
        refreshToken
        expiresIn
    }
}
# DE_API_EMAIL and DE_API_PASSWORD are injected by the runtime from the secret
# manager. They are never written into this file, and `jq --arg` keeps them off
# the command line, out of shell history, and correctly JSON-escaped.
jq -n \
  --arg query 'mutation Login($email: String!, $password: String!) { login(email: $email, password: $password) { token refreshToken expiresIn } }' \
  --arg email "$DE_API_EMAIL" \
  --arg password "$DE_API_PASSWORD" \
  '{query: $query, variables: {email: $email, password: $password}}' \
| curl -sS -X POST "$GRAPHQL_ENDPOINT" \
    -H "Content-Type: application/json" \
    --data-binary @-

In application code, read the values from the secret client at startup (and on rotation), pass them as GraphQL variables, and keep them out of any structure you serialize into logs or error reports.

2.4 Refresh proactively, on a schedule, not reactively on a 401

The documented lifetimes are:

TokenDocumented lifetime
Access token~15 minutes (expiresIn: 900)
Refresh token~12 hours

A backend client should treat these as a schedule, not as an error condition to discover:

  1. On login, record the issue time and the returned expiresIn. Trust the response value rather than hardcoding 900; expiresIn is what the server actually granted.
  2. Refresh the access token when a comfortable fraction of expiresIn has elapsed; well before expiry, leaving room for clock skew, retries, and the round trip itself.
  3. Re-authenticate with login before the ~12 hour refresh window closes, so the refresh session never lapses mid-workload.
  4. Serialize refreshes. If many workers share one account, one process should refresh and publish the result; concurrent refreshes from a pool are a common source of self-inflicted Invalid refresh token errors.

Waiting for a 401 to trigger a refresh guarantees that a portion of your traffic fails first. For a batch job that only runs every few hours, it also guarantees the first call of every run fails, because a ~15 minute access token cannot survive between runs.

2.5 Handle auth failure as: one retry, then re-authenticate, then stop

request → 401 / "Token verification failed"
        → refresh once
        → replay the request once
              ├── success → continue
              └── still failing → full login
                                    ├── success → continue
                                    └── failure → stop, alert a human
  • Refresh once. Do not loop. A repeated refresh against an invalid token is not going to succeed on the third attempt.
  • Invalid refresh token or Refresh token expired means re-login, not retry. The error meanings are listed in Authentication.
  • Incorrect email or password is terminal. Never retry it in a loop; page a human. A credential is either right or wrong; looping only buries a real credential problem under noisy failures. Security & compliance §8.10 documents a failed-login lockout policy (rolling 24 h window: 3 failures = 15 min, 4 = 1 h, 5 = locked until an administrator re-enables); a looping bad deploy can therefore lock your integration account out entirely. (This error is observed on the wire today with extensions.code: INTERNAL_SERVER_ERROR; match on the message, not the code, when detecting it.)
  • Do not retry business rejections. A REJECTED decision will not change (Performance & uptime).
  • The API may return 429 under excess volume even though there is no published global rate limit; back off there rather than tightening the loop (Error handling).

2.6 Separate credentials per environment

Use a distinct robot account, and a distinct secret-store entry, for every environment. "Keep separate tokens per environment; do not reuse production credentials in lower envs" (How to test in other environments).

In practice:

  • One secret path per environment, resolved from the deployment's own configuration; so a misconfigured staging deploy cannot reach production data.
  • Never paste a production token into Apollo Sandbox to debug a lower environment. A token is only valid for the environment that issued it, and pasting it into a browser tool widens its exposure for no benefit.

2.7 Concurrency: confirmed safe to scale out

There is no session limit. A second login call for the same robot account (a second worker process, a second pod, a horizontal scale-out) does not invalidate the first session's tokens. Concurrent logins for one account are supported, so per-instance login (each worker authenticates independently) is a safe pattern; you do not need a single shared token holder to avoid workers kicking each other off.

The refresh token rotates on every use. Each call to refreshToken returns a new refresh token, and implementers must persist the returned value and use it for the next refresh; reusing a stale refresh token will fail. This still means refreshes should be serialized per worker (a worker should not race itself across two in-flight refreshes), but it is no longer a reason to centralize login across workers: rotation is a per-call contract, not a single-session lock.


3. Rotation and revocation

Stated without spin, because this is where a vendor review will press.

What is confirmed

  • Password rotation is possible, and it is the primary rotation lever. An account's password can be changed, and administrators holding Users.update can edit users (Managing users). Self-service password reset mutations (requestPasswordReset, resetPassword) also exist in the schema, so a robot account's credential can be rotated without a support ticket. Nothing in this documentation set defines a required rotation interval or a maximum credential age for API accounts; that part stays open (see below).
  • Access is removable through roles, and it is immediate at the API layer. Users cannot be deleted; the documented method is to assign a Disabled role, which "has no access to the platform or any actions within it" (Type of roles, Managing users). Confirmed: once an account is Disabled, API calls made with that account's existing, unexpired token return a forbidden/authorization error immediately; the role check happens on every call, not only at login. This is the one containment lever a customer administrator can pull without opening a ticket, and it works right away at the permission layer.
  • State this honestly: the token itself is not revoked. Disabling an account (or changing its password) stops that account from being able to do anything, but it does not invalidate the JWT signatures already issued. A token issued before the change remains cryptographically valid until it naturally expires (up to ~15 minutes for an access token and up to ~12 hours for a refresh token) even though every call it makes will now be rejected as forbidden. The real containment window is therefore effectively immediate for authorization (the token can't do anything) but the token itself is not revoked and does not disappear from a systems standpoint until expiry.
  • There is no self-service session-termination mutation. Introspection of the unauthenticated schema shows login, refreshToken, auth, exchangeSsoHandoffCode, requestPasswordReset, and resetPassword; no mutation to revoke a specific refresh token or enumerate/terminate active sessions. Revoking a session outside of disabling the account or changing the password is a support request; Authentication says to "contact support for guidance."
  • Tokens are short-lived by design. An access token expires in ~15 minutes and a refresh session in ~12 hours, which caps the outer bound of exposure even if no containment action is taken at all.

What is still open

  • There is no published response target for a leaked credential. The support SLA table is priority-based and general (Critical is a 1 hour response and 6 hour resolution target) but no page classifies a credential compromise at a priority, and no page states a target time for a support-mediated revocation.

What to do given this

Disabling the account (or rotating the password) stops damage immediately at the API layer, but does not revoke the token as a credential; plan around both facts:

  1. Rotate the password on a schedule you own, using resetPassword/requestPasswordReset or an admin edit, and treat that rotation as the primary containment action available to you.
  2. On suspected compromise, assign the Disabled role first: it is the fastest lever and takes effect on the next API call; then rotate the password, and open a Zendesk ticket if you also want the outstanding token sessions cleared rather than just left to expire.
  3. Don't assume the old token is gone. Until it naturally expires (≤15 minutes access, ≤12 hours refresh), it still exists as a signed credential; Disabled just means it can no longer do anything through the API. If that residual signature matters for your compliance posture, say so in the Zendesk ticket.
  4. Keep the blast radius small: one account per integration, least-privilege role, per-environment separation. Scope limits what a token can reach even during its containment-but-not-yet-expired window.
  5. Alert on authentication anomalies you can see yourself: an unexpected login failure rate, a burst of forbidden errors right after you disable an account (expected), or calls from a deployment that should not be running.

4. If your security team asks

What this documentation set does and does not currently answer. Anything marked not covered here may still exist; it is simply not published in these docs, so ask your account manager rather than inferring it from this page.

QuestionWhere it stands
CertificationsISO 27001, SOC 2 Type II, GDPR, PCI are listed in Security & compliance.
How Deal Engine stores credentials internallySecurity & compliance states credentials are encrypted at rest in corporate systems with RBAC and MFA under ISMS procedures.
Encryption in transitTLS 1.3 for the API and webhook deliveries; see Security & compliance §8.7. All published endpoints are https://.
Machine-to-machine credential typeConfirmed: none exists, and none is on the roadmap. Password-based login is the supported pattern. See section 1.
Self-service token revocationNo mutation to revoke a specific session; disabling the account (self-service, via role change) blocks API access immediately but does not invalidate the token signature until natural expiry. Full session-list revocation is support-mediated. See section 3.
Access control modelRoles and permissions are documented in Type of roles and Role settings. Permissions are group.name pairs applied at the root operation level, not per nested field; a specific set of operations is permission-gated and the rest require only a valid token; see Security & compliance §8.10.
Uptime and status99.9% standard uptime; status.deal-engine.com (Performance & uptime).
Rate limitsNo global public limit; throughput calibrated per client, 429 possible (Error handling).
Support and escalation SLAsPriority-based table in the Zendesk guide. Not security-incident specific.
Customer-visible authentication audit logsNot covered here.
Password complexity, lockout, and failed-login throttling policyLockout thresholds documented in Security & compliance §8.10; password composition policy not published.
IP allowlisting for API clientsNot covered here.
Breach notification commitment, DPA, subprocessor list, penetration test reports, data retention periodsNot covered here; contractual documents, request them through your account manager.

Webhook delivery egress IPs are published in the Webhooks guide (Network requirements).


5. Schema visibility differs for a Robot Account

The schema Apollo Sandbox displays is role-aware (How to test in other environments). Unauthenticated, it shows only public operations such as login. With an Authorization header and a Refresh Schema, it shows what that token's role can reach.

This has a specific consequence for backend integrations: a Robot Account will generally see a smaller schema than the human who scoped the work. An operation an administrator found in the Docs panel may simply not appear for the robot's token, and the stock Robot Account role is read-only, so write operations are the usual casualty.

If an integrator reports that an operation "does not exist":

  1. Confirm the token used for Refresh Schema is the robot account's, not a personal admin login.
  2. Confirm the token matches the environment of the endpoint, and has not expired.
  3. Confirm the role includes the permission for that operation; if it does not, the operation is invisible rather than merely forbidden.

Absence from the schema is a permissions symptom far more often than a missing feature. Check the role before filing a schema bug.


6. Summary of current limitations

For a reviewer who reads only one section:

  • The only credential for a non-interactive client is a user email and password; confirmed, and no API key, service account, client_credentials grant, or mTLS option is planned.
  • The password must remain retrievable at runtime, because a ~12 hour refresh window forces periodic re-authentication. Self-service password reset mutations (requestPasswordReset, resetPassword) exist, so rotation does not require a support ticket.
  • The refresh token rotates on every use; persist the value returned from each refreshToken call. There is no session limit; concurrent logins for the same account do not invalidate each other.
  • Disabling an account (the Disabled role) blocks API access immediately: every call with that account's token is rejected as forbidden from the next request on. It does not revoke the token's signature: a token issued before the change stays cryptographically valid until natural expiry (≤15 minutes access, ≤12 hours refresh). Full session termination beyond that is a support request.
  • There is no published response target specific to a leaked credential.
  • Compensating controls that are available today: a dedicated least-privilege account, secret-manager storage, per-environment credential separation, short token lifetimes, customer-controlled password rotation (self-service or admin), and immediate role-based access blocking.

Related pages


Did this page help you?
All rights reserved © 2025 deal-engine.com.