Redmine REST API integrations: what they involve and where they break
A team that wants Redmine to exchange issues, projects, users or time entries with another system is usually facing a design problem before a coding problem. The work is mostly about choosing the right authentication method, the right sync direction and the resources that are stable enough to build on, and those choices should be settled before anyone writes an integration script. Getting them wrong is what makes these projects expensive, not the HTTP calls themselves. The Redmine REST API is off by default. An administrator must enable it in the application settings before any client can call it. It returns data in JSON or XML, and clients authenticate either with HTTP basic authentication or with a per-user API key, which can be sent as a request parameter or in the X-Redmine-API-Key header. Core resources such as issues, projects, users and time entries are marked stable in the API documentation. Confirm that list against the exact Redmine version you run, and check the resource pages for any field that is marked otherwise before you depend on it. Teams usually need this work for three reasons: feeding reporting or dashboard tools from Redmine data, capturing time from other tools into Redmine time entries, and creating tickets from a service desk or monitoring system. Each use case implies a different sync direction. A reporting feed is one-way and tolerant of delay. Time capture and service desk ticket creation are two-way or write-heavy, which means you must decide which system owns each field and what happens when both sides change the same record. The hard parts are predictable. Duplicate records appear when a retry or a timeout creates an issue twice, so the integration needs a stored mapping between external and Redmine identifiers, or a deliberate lookup before create. Lost updates happen when two writers edit the same issue and the later write silently overwrites the earlier one, so you should compare timestamps or version information before writing. Keys that outlive their owners are common: an API key tied to a user who leaves the organization can break every integration that uses it. Assign integration keys to dedicated service accounts with the minimum role they need, and record who owns each key and when it must rotate. Finally, the integration will change with each Redmine upgrade. The upgrade procedure involves migrating the database and plugins, and API behavior can shift between lines, so an automated test suite that exercises your real calls against a staging copy of the current version is the only reliable way to know the integration still works. Plan that suite and its environment as part of the build, not as an afterthought.
Which Redmine settings and API surfaces the integration depends on
A Redmine REST API integration depends on a small set of administrator settings, authentication choices and resources, and each one shapes what the integration can read and write. Reviewing these components before design work prevents most access surprises during build and testing.
Administrator switch for the REST API
The REST API is disabled until a Redmine administrator enables it in the administration settings. Without this step, every request fails regardless of credentials, so it should be the first item in any integration checklist. Confirm the setting on the exact installed version, because the location of the option and related defaults can differ between release lines. Record who owns this setting, since changing it affects every integration on the instance.
Per-user API keys in account settings
Each Redmine user can generate an API key from their own account settings page. An integration that runs under a named service account should use that account's key, not the key of a person who may leave or change roles. Ask who will rotate keys, where they will be stored, and how a revoked key is detected by the integration.
HTTP basic authentication
The API also accepts HTTP basic authentication, which uses the username and password of the account. This suits some scripted tools but ties the integration to a password that may be subject to expiry or multi-factor policies configured in the instance. Decide early whether basic authentication is permitted at all, and confirm with the administrator whether it works with any LDAP-backed accounts in use.
Key as parameter or header
The API key can be sent as a request parameter or in the X-Redmine-API-Key header. Parameters can appear in web server access logs, proxy logs and monitoring tools, so the header is usually the safer choice for production calls. Check how the reverse proxy and logging stack handle custom headers and confirm the accepted method against the API wiki for the installed version.
JSON and XML response formats
Responses are available in JSON and XML, selected by the request format. The integration's parser, error handling and test fixtures must match the chosen format. Pick one format per integration and test it against the installed version, because field presence and nesting for optional attributes should be verified rather than assumed.
Stable core resources
Issues, projects, users and time entries are the core resources marked stable in the API documentation, and they are the safest base for a build. Other resources and plugin-provided endpoints may change between releases. Limit the first integration to these resources and confirm endpoint behavior, filters and update rules in the API wiki for the version you run.
Role and membership limits
The integration account's role and project memberships decide what the API returns and what it can change. A request that looks correct can return fewer issues than expected when the account cannot see a project or tracker. Test with the same account the production integration will use, and document which projects and permissions it needs.
Custom fields through issue data
Custom field values are not a separate resource you query on their own. They appear within the issue data they belong to, and only when the account can see that field on that tracker and project. Map each custom field to the target system during design, and retest after any change to field visibility or tracker configuration.
Which authentication method, sync direction and integration pattern to choose
This table compares the authentication methods, sync directions and integration patterns for Redmine REST API work, with the operational cost each one leaves behind. Read each row as a trade-off: the option that is quickest to build is often the one that costs the most to keep running after staff changes or Redmine upgrades.
| Decision | Option | Operational cost | When it fits |
|---|---|---|---|
| Authentication | Per-user API key tied to a named person | Key must be rotated or reassigned when that person leaves or changes role; actions are logged under their name | Small scope where the integration acts on behalf of one accountable user |
| Authentication | Dedicated integration user with a narrow role | One account to manage and review; role permissions must be kept to the minimum the sync needs | Recurring syncs that should survive staff turnover and keep audit trails separate from people |
| Authentication | HTTP basic authentication | Credentials travel with every request and need a secure store; weakest fit for long-lived automation | Quick tests or legacy callers that cannot send an API key header; confirm HTTPS is enforced first |
| Sync direction | One-way pull from Redmine | Low conflict risk; the other system must handle stale data between pulls | Reporting, dashboards or read-only mirrors of issues and time entries |
| Sync direction | One-way push into Redmine | Low conflict risk; Redmine becomes the destination and duplicate creation rules must be defined | Feeding issues from monitoring, service desk or forms into a tracker |
| Sync direction | Bidirectional sync | Highest ongoing effort: needs conflict rules, a system of record for each field and reconciliation after failures | Only when both systems must edit the same fields and users accept a defined winner for each conflict |
| Integration pattern | Direct polling from the other system | No extra component to host, but every system carries its own retry, paging and error handling | Simple, low-volume flows with one owner and few fields |
| Integration pattern | Middleware or scheduled job runner | Another component to patch, monitor and back up; central place for mapping rules and logs | Several systems or flows that need shared transformation, retries and an audit trail |
| Integration pattern | In-Redmine plugin | Must be reviewed and maintained across each Redmine upgrade and Ruby or Rails change; compatibility checks before every upgrade | Logic that core Redmine cannot express through the API or existing settings |
Which Redmine REST API resources are stable enough to build on
The REST API wiki marks core resources such as issues, projects, users and time entries as stable. Build your integration on those first. Other resources, endpoints added by plugins, and fields that plugins add to a payload do not carry the same guarantee, so treat them as something to verify before you depend on them. Before you commit to any resource, open the stability markers in the REST API wiki and read them against the exact Redmine version you run, because the wiki describes the API as it is documented for that release line, not as a permanent contract.
Pin the Redmine version you test against and record it next to your integration code. The supported lines differ in how long they receive fixes: the 7.0.x line gets new features, bug fixes and security updates, 6.1.x gets only bug fixes and security updates, and 6.0.x gets only important security updates. A production instance on an older line can therefore drift from the version your tests ran on, even when no one upgrades on purpose. Make your test environment match production's version exactly, and treat any version change as a trigger to rerun the integration tests before go-live.
Stability of the resource does not guarantee stability of the payload. Custom fields are configured by administrators, and a custom field can be added, renamed, marked required or made visible only for certain trackers. Tracker and workflow changes can also alter which fields appear on an issue. Each of these changes can add keys to a response, remove keys for some records, or change the value format an integration expects. An integration that reads fields by fixed position or rejects unexpected keys will break on a change that an administrator considers routine.
For that reason, design the consumer to tolerate unknown fields. Read the attributes you need by name, ignore keys you did not request, and handle missing optional values without failing the whole record. Where you write data back, send only the fields you intend to change. Ask the Redmine administrator who owns custom fields and trackers to notify the integration owner before changes, and keep a list of the custom field IDs your integration uses so that a deleted or renamed field is detected quickly.
Anything beyond the resources the wiki marks as stable needs confirmation. Check the plugin's own documentation, its compatibility notes for your Redmine line, and its behavior in your environment, and ask the plugin maintainer how it handles API changes. Record the result as an explicit decision in your integration design, so that the team knows which endpoints are covered by the stability markers and which rely on your own tests.
Redmine REST API integration build sequence, step by step
This sequence takes an integration from a confirmed Redmine version and API setting to a scheduled production cutover, with a check at each step so the sync can be rerun safely and reversed if needed.
Confirm the target Redmine version and support line
Confirm which Redmine release the integration will target and whether that release sits on a supported line. Redmine 7.0.x is the fully supported line, 6.1.x receives only bug fixes and security updates, and 6.0.x receives only important security updates. Check the stable releases on the Redmine download page and record the version and support status in the integration design. Done when the target version, its supported line and its Ruby and database requirements are written down and agreed with the Redmine owner.
Have an administrator enable the REST API
A Redmine administrator turns on the REST API in the administration settings, under the API section. Nothing else can authenticate until this is set. The integration owner should confirm the setting in the administration area and then make an authenticated test request from a workstation to a core resource such as the projects or issues endpoint. Done when the test request returns data in the expected format, with the administrator and date of the change recorded in the change log.
Create a dedicated integration user with minimal access
Create a separate Redmine user account for the integration instead of reusing a person's account. Give it the narrowest role that allows the required actions, then add it as a member only to the projects the sync touches. Review which trackers, statuses and time entry activities the role can use, because the sync will fail or write to the wrong place if those are too broad or too narrow. Done when a permissions review confirms the account can read and write only the agreed projects and cannot reach anything else.
Issue and store the API key outside source code
Generate the API key for the integration user from its account page and load it into the secret store your platform uses, such as a managed vault or encrypted environment configuration. Redmine accepts the key as a request parameter or in the X-Redmine-API-Key header, so prefer the header and keep it out of logs and URLs. Document who can rotate the key and how the integration reads it at runtime. Done when a scan of the repository and the job configuration finds no key in plain text and a test request succeeds using the stored secret.
Map external identifiers to stored references
Decide how each record in the external system links to its Redmine counterpart. Use a Redmine custom field on issues and time entries that holds the external identifier, or keep a mapping table in the integration's own database. The sync must look up this reference before creating anything, so a rerun updates the existing record instead of creating a duplicate. Done when test runs against sample data show that repeating the same job produces no new Redmine issues or time entries and that every created record carries its external reference.
Build issue and time entry sync with explicit conflict rules
Build the sync for issues and time entries using the stable core resources. Write down the conflict rules before coding: which system owns the subject, status, assignee and hours, what happens when both systems changed the same field, and whether deletions propagate or only mark records for review. Check that time entries use activities the integration user is allowed to log against. Done when the rule set is signed off by the business owners and each rule has a test case that shows the expected outcome.
Add error handling, logging and retries
Wrap every API call with handling for authentication failures, permission errors, validation rejections and timeouts. Retry only errors that are transient, and send validation rejections to a review queue with the payload and response recorded. Log each run with counts of created, updated, skipped and failed records, and never write the API key or personal data into logs. Done when a deliberately broken test case, such as an invalid tracker or a revoked key, produces a clear log entry, leaves no partial state and is reported to the owner.
Run the sync against a non-production copy
Restore a copy of the production Redmine database and attachments into a non-production environment running the same Redmine version and plugins. Point the integration at that copy and run full and repeated syncs, then compare record counts, statuses, assignees and time totals with the source system. Confirm the non-production copy is not connected to live external systems or email delivery. Done when the reconciliation report shows no unexplained differences and the rerun check from the mapping step still passes.
Schedule the production cutover with a rollback path
Agree a cutover window with the business owners and freeze changes to the in-scope records during it. Take and verify a database and attachment backup immediately before the first production run, then run the sync, reconcile the results and keep the integration in a read-only or paused state until reconciliation passes. Document the rollback path, including how to disable the job, restore from the backup and retire the integration user's access. Done when the cutover is signed off, the rollback steps have been rehearsed on the non-production copy and the owner confirms the scheduled job is live.
What drives the effort and cost of a Redmine REST API integration
This table lists the factors that change how much design, build, testing and upkeep a Redmine REST API integration needs, with ways to keep each one under control. Use it to see where a written quote will be sensitive to scope, and which questions to ask before agreeing a fixed-scope plan.
| Driver | Why it changes effort or cost | How to keep it down |
|---|---|---|
| Number of source systems and data models | Each system adds its own identifiers, field types, status models and failure modes that must be mapped and tested. | Start with one source system and one direction. Add systems only after the first mapping is validated. |
| Sync direction and conflict handling | Two-way sync needs rules for which change wins, plus logic to avoid update loops and duplicate issues. | Use one-way sync where possible, or assign one authoritative system per field so no conflict rule is needed. |
| Volume of issues and time entries | Large histories extend initial backfill, API rate handling, retries and reconciliation runs. | Limit the first load to open or recent records and plan the historical backfill as a separate, timed job. |
| Stable versus unstable API resources | Core resources such as issues, projects, users and time entries are marked stable in the Redmine REST API documentation. Less common resources or plugin endpoints may change between releases and need more retesting. | Build on stable core resources first. Confirm the status of any plugin endpoint with its maintainer before design starts. |
| Number of projects, trackers and custom fields | Every custom field mapping, tracker and status value needs a translation rule and a test case. | Reduce mapped custom fields to those the other system needs. Keep the rest inside Redmine. |
| Plugin dependence | Plugins that add fields or endpoints must be checked on every Redmine upgrade, and their API behavior may differ from core. | Prefer core fields and endpoints. Record each plugin the integration relies on and include it in upgrade testing. |
| Identity mapping between systems | Matching users across directories, handling deactivated accounts, and assigning API keys to the right service or person all require design and review. | Map users on a stable identifier, such as an email address or directory ID. Use a dedicated integration account with limited roles. |
| Environments (test, staging, production) | Each environment needs its own Redmine instance, API key, data set and deployment step. Keeping them aligned takes ongoing work. | Automate the deployment and seed test data from anonymized copies. Keep the number of environments the team can actually maintain. |
| Compliance evidence, such as API write logs | Assessors may expect a record of which account made which change through the API, how keys were issued, and how access was reviewed. | Agree the evidence list before build. Log writes at the integration layer and keep Redmine's own history enabled. |
| One-time costs: design, build, testing, cutover | These cover field mapping, sync logic, error handling, test runs, parallel running and the cutover plan. They are paid once per integration, but rework adds to them. | Freeze the mapping in writing before build. Ask the provider to state which test cases and cutover steps are included. |
| Recurring costs: key rotation, monitoring, retesting | API keys need rotation and revocation, sync jobs need monitoring, and each Redmine upgrade requires regression testing of the integration. | Ask for a named owner for each recurring task. Confirm whether these are covered by a retainer or by separate work. |
How to handle Redmine API keys and integration accounts safely
The most important decision in a Redmine integration is not which endpoint to call but how the credential that makes the call is issued, stored and watched. Redmine authenticates REST API requests with HTTP basic authentication or a per-user API key, and the key can be sent either as a request parameter or in the X-Redmine-API-Key header. Those two transport options are not equivalent from a logging standpoint. A key placed in a query string is part of the URL, and URLs are routinely written to web server access logs, reverse proxy logs and load balancer logs. Plan to use the header method for every integration, then check the logging configuration of each layer in front of Redmine (web server, proxy, load balancer and any monitoring agent) to confirm that request headers and query strings are handled as you expect. Ask your Redmine implementation partner or your own platform team to show you a sample log line for an authenticated API call before go-live.
Transport and storage come next. All API traffic should run over HTTPS, because both basic authentication and API keys are credentials that are readable by anyone who can observe the connection. Confirm that the Redmine host name, any reverse proxy and any load balancer enforce TLS, and that the integration client refuses plain HTTP rather than following a redirect silently. Keys should not live in source code, configuration files committed to version control, or chat messages. Store them in your organization's secrets manager, inject them into the integration at runtime, and define a rotation procedure before the integration goes live. Rotate keys whenever someone who knew them leaves or changes role, and whenever a key may have appeared in a log, ticket or screenshot. Rotation is only practical if the integration can accept a new key without a code change, so make that a requirement in the build.
Ownership of the key is the decision most teams get wrong. A key belongs to a Redmine user, and every action the integration takes is recorded as that user. If a production sync runs under a developer's personal key, the sync stops when that person's account is locked, their role changes or they leave, and the audit trail shows the developer as the author of automated changes. Create a dedicated integration user for each integration or each environment, with its own key, and keep it out of the group of people who sign in interactively. Then restrict that user's role to the permissions the sync needs and limit its project membership to the projects it touches. Review the role's permissions against the actual API calls the integration makes, because a role that is broad enough for an administrator to troubleshoot is usually far broader than a sync requires. Note that the REST API has to be enabled by an administrator in the Redmine settings, so the list of people who can switch it on is part of the same control set. Keep that list short and review it periodically.
Audit is where these controls prove themselves. Redmine records the user associated with each change in the issue history and time entries, so an integration account that is named clearly (for example, a name that states the system and environment) makes automated writes easy to identify and distinguish from human edits. Decide before build what the integration writes and whether each write should carry a note or custom field identifying the source system, so reviewers can trace a change back to the sync job that made it. Confirm with your security or compliance team which log retention periods apply to Redmine application logs, web server logs and the database, because the audit value of an account name disappears if the underlying logs are rotated out before a review. Server-wide hardening, including password policy, session settings, file upload limits and plugin review, is covered in the Redmine security hardening topic, which should be read alongside this one.
What to monitor, rotate, retest and document after go-live
Once a Redmine REST API integration is live, its risks shift from build defects to drift: keys age, Redmine changes underneath the sync, and the mapping between systems falls out of date. The six areas below are the recurring work that keeps the integration correct after cutover, and each one needs a named owner, a check and a record that someone can audit later.
Monitor API errors and sync lag
Track HTTP error responses from the Redmine REST API, failed sync runs and the age of the newest record the sync has processed. Authentication failures usually point to a changed key or a disabled account, while growing lag often points to a stalled queue or a slow query. Set alerts on failed runs and on lag beyond the window your users expect, and review the log for repeated 403 or 422 responses after each Redmine change.
Rotate integration API keys
Each integration should authenticate as a dedicated Redmine user with its own API key, so a rotation affects one connector only. Rotate keys on a fixed schedule and again whenever someone who knew the key leaves or changes role. Store the new key in your secrets manager and revoke the old one only after the sync confirms a successful run. Confirm the connector sends the key in the X-Redmine-API-Key header, because keys passed as URL parameters can be recorded in proxy and server logs.
Retest after Redmine upgrades
Run the integration test suite after every Redmine upgrade and every plugin install or update, since a changed custom field, workflow or API response can break a sync without any error on the Redmine side. First, check which version line the installed Redmine is on against the supported branches on the Redmine download page. The 7.0.x line is fully supported, 6.1.x receives only bug and security fixes, 6.0.x receives only important security fixes, and older lines are unsupported. Ongoing testing and upgrades can be covered by a support retainer, with scope agreed in writing.
Keep a mapping document
Maintain a versioned document that lists each external ID, the Redmine custom field or tracker it maps to, the status and priority translations, and the conflict rules that decide which system wins when both change the same issue. Without it, a developer who changes a field later cannot tell whether a difference is a bug or a designed rule. Review the document whenever a custom field or workflow changes in Redmine, and keep it in the same repository as the integration code.
Back up the sync state store
The sync keeps its own state, such as cursors, last-seen timestamps and pairs of external and Redmine IDs. Back it up on the same schedule and retention as the Redmine database and attachment files. Restoring Redmine alone leaves the sync unable to tell which records it already handled, which can cause duplicate issues or missed updates. Test a restore of both together, and confirm the two backups were taken close enough in time to match each other.
Record ownership of the integration account
Write down who owns the Redmine user behind each integration, who can approve key rotation, who receives alerts, and who may change the mapping document. Name a primary person and a backup contact, not a shared mailbox alone. Review the record at each staff change and each Redmine upgrade, because an integration account that nobody recognizes is often one that keeps running with broad permissions long after its purpose has changed.
Questions buyers ask before building a Redmine REST API integration
How much does a Redmine REST API integration cost?
The cost is driven by scope, since Redmine is free under GPL v2 and has no paid edition, so you pay for people and infrastructure. Most of the spend is labor: scoping the sync rules, building and testing the integration, and then keeping it working. Cost rises with the number of objects synced (issues, time entries, users, custom fields), the number of external systems involved, the conflict rules you need, and the number of environments that must be tested. Recurring costs include maintaining API keys, reviewing the integration after each Redmine upgrade, and watching for failed syncs. When you request a quote, ask it to separate one-time build work from recurring support and to list the Redmine version and resources in scope.
How long does a Redmine API integration take to build?
Duration follows scope, and no fixed timeline fits every integration. A one-way sync of issues into another system is a different job from a two-way sync of issues, time entries and users with conflict handling. The main drivers are the number of resources, how many custom fields and trackers must be mapped, how many workflow states exist, whether a test instance is available, and how quickly your owners answer mapping questions. Delays usually trace back to unclear conflict rules and missing sample data, and the API itself is rarely the cause. Ask for a plan that lists the phases, the resources in scope, and what you must provide before each phase starts.
What do we need to provide for a Redmine integration project?
Before work starts, your team should prepare five things:
- API access approval: an administrator must enable the REST API on the Redmine instance, and someone must decide which account the integration uses.
- A test environment: a non-production Redmine on the same version you run, with copies of the relevant trackers, workflows and custom fields.
- Named owners: one business owner for the sync rules and one technical owner for Redmine and the external system.
- Sample data: representative projects, issues and time entries, including edge cases such as closed issues and deactivated users.
- Decisions on conflict rules: which system wins when both change the same field, and how disputed changes are logged.
Missing any of these usually moves the schedule, so confirm them in the written scope.
Can an internal team build a Redmine API integration alone?
Yes, an internal team can build it alone if it can test against stable resources and maintain the keys and upgrades over time. Core resources such as issues, projects, users and time entries are marked stable in the Redmine REST API documentation, which makes them a safer base for a build. The team also needs to issue and rotate API keys, check the integration after each Redmine upgrade, and confirm behavior on the version in use. The bigger risk is maintenance: if the only person who understands the field mapping leaves, the sync becomes fragile. If you bring in a partner for the build, ask them to hand over documentation, automated tests and a runbook.
Does the Redmine REST API cover every object and field?
No. The REST API covers the core resources, and issues, projects, users and time entries are marked stable, but not every object, plugin resource or custom field is exposed in the same way. Plugin endpoints depend on the plugin's own API, and some fields or filters may behave differently between versions. Before you design the integration, list every object and field the sync needs, then check each one against the REST API documentation for the Redmine version you run. Test reads and writes on a non-production instance, and confirm with plugin authors what their endpoints guarantee.
Does a Redmine API integration need SSO or LDAP for the integration account?
Not necessarily. The Redmine REST API authenticates with HTTP basic authentication or a per-user API key, which is separate from how people sign in to the web interface. The integration account can be a dedicated Redmine user with its own role and API key, limited to the projects and actions the sync needs. Whether that account should come from LDAP, or whether SSO applies to it, is a separate decision that we cover in our Redmine LDAP and SSO integration topic. Confirm with your identity team how service accounts are created, reviewed and rotated before you choose an approach.
Which Redmine versions should an API integration target?
Target the 7.0.x line if you can, since it is the fully supported branch with new features, bug fixes and security updates. The 6.1.x line receives only bug fixes and security updates, the 6.0.x line receives only important security updates, and 5.1.x and older are unsupported. Redmine 7.0 runs on Rails 8.1 and supports Ruby 3.2, 3.3, 3.4 and 4.0, and it does not support JRuby. Record the version you will actually run in the integration design, and plan a compatibility check before each Redmine upgrade. An administrator must also enable the REST API on that instance.
How DeployOpen works on this
DeployOpen treats each Redmine REST API integration as a written, fixed-scope engagement inside your own infrastructure. We agree the systems, resources and sync rules up front, then build, test, document and hand over the integration so your team can run it.
How we scope
- Confirm the Redmine version and whether the REST API is enabled by an administrator before any design starts.
- Map which resources the integration needs, such as issues, projects, users and time entries, and confirm each is stable for your version.
- Agree in writing the sync direction, identity model for API keys or service accounts, and the systems on each side of the integration.
How we build
- Build against stable core resources and use per-user API keys or a dedicated account with roles limited to the required actions.
- Handle issue and time entry sync with explicit mapping of trackers, statuses, custom fields and error handling for rejected requests.
- Test in a non-production Redmine instance with representative projects, then verify create, update and failure paths before go-live.
How we hand over
- Deliver the integration code, configuration, key-handling procedure and a record of the resources and permissions it uses.
- Document the mapping tables, scheduled jobs, logs and rollback steps so your administrators can operate the integration without us.
- Walk your team through the runbook and agree the acceptance tests that close the engagement.
How we support
- Offer ongoing support as a retainer covering monitoring, fixes and reviews when Redmine or the connected systems change.
- Check the integration against each Redmine upgrade, including Ruby and Rails changes in the 7.0 line, before your upgrade window.
- Rotate API keys, review access and re-test sync behavior on a schedule agreed with your team.

