Amend a contract's terms
Changes an ongoing contract's terms (per-period amount, frequency, number of occurrences), moves its next charge, or sets a keep-active-until date that holds the contract open past its final payment. A change to the money terms replaces the contract: a new contract is created with a parent link, unpaid charges and in-flight payments move to it, and the original is cancelled with a link to its successor. With the modify-in-place flag the same change is applied to the contract itself. Non-money changes (next charge, keep-active-until, and the descriptive fields the same call accepts) are always applied in place.
Where you can do this
- Merchant View: Edit a contract (Save)
- API: Update a contract (
PUT /contracts/{contract_id}) — body carries amount, frequency, occurrences, next_charge or keep_active_until; optional modify-in-place flag
Before you start
- The contract is an ongoing type (recurring known amount, recurring variable amount, or scheduled) and is not completed, cancelled, failed or cancelling. A scheduled contract's instalments are amended through Amend a scheduled contract's instalments; this function's amount and frequency inputs do not apply to it.
- The contract is not suspended, cancelled or cancelling with a next_charge in the same body; that combination is a resume (Resume a contract).
- In Merchant View the contract is in progress (ACTIVE, PENDING, SUSPENDED, UNRESOLVED or FAILING), recurring or scheduled, and not superseded by a replacement; the user holds
payment_contract.
Inputs
| Input | Required | Meaning and constraints |
|---|---|---|
| amount | no | New per-period amount. Not accepted on a recurring variable-amount contract. A value equal to the current amount is not a terms change. |
| occurrences | no | Total number of charges over the contract's life, including those already created. Cannot be below the charges already created; equal is accepted and ends the contract once nothing is outstanding. Only a positive number is forwarded: a null or a zero is dropped before Shuttle, so the field can raise or lower a cap but cannot remove one (see Rules). |
| next_charge | no | Date of the next charge, or the literal now. Must be in the future. Rules and Merchant View's five-minute guard on the dedicated screen are on Move the next charge; on this screen the picker only floors the date at the start of today. |
| keep_active_until | no | Date until which the contract is held active after its last charge is settled. Any date is accepted; an elapsed date is a completion trigger. An empty string is documented as clearing it but does not reach a working path (see Rules). |
| modify_in_place | no | true applies an amount, frequency or occurrences change to this contract instead of creating a replacement. It is read only when one of those three is actually a terms change; on a body that changes nothing but dates or descriptive fields there is no replacement to suppress, so it has nothing to do. Refused on a resume, and refused alongside status. |
| description | no | Goods or service description. Applied to the contract in place, or to the replacement when one is created. An empty string is dropped rather than clearing the description. |
| alt_key | no | Your reference for the contract (Shuttle's basket key). Applied in place or to the replacement; a change also re-queues the contract's transactions for re-indexing. An empty string is dropped rather than clearing it. |
| metadata | no | Custom key-value pairs stored on the contract; the map sent replaces the stored one wholesale. Applied only when the same body also carries description, alt_key, next_charge or next_payment, or — on an in-place edit — amount, frequency or occurrences. On its own, or with only keep_active_until, it is silently dropped (see Rules). |
| event_date | no | The date the contract relates to. Applied only when the same body also carries description, alt_key, next_charge or next_payment, or — on an in-place edit — amount, frequency or occurrences. On its own, or with only keep_active_until, it is silently dropped (see Rules). |
| payment_method | no | Handled by Change the payment method on a contract; when a replacement is created it is applied to the replacement. |
| next_payment | no | Handled by Reschedule the next payment. |
| status | no | Target contract status, and not an amend: SUSPENDED on its own is a suspend (Suspend a contract) and ACTIVE on a suspended contract is a resume (Resume a contract). On the amend path it reaches Shuttle only when bypass_validation is sent with it, and Shuttle then sets the status, raises CONTRACT.UPDATE and returns without applying anything else in the body. |
Rules
Checked by the API means Shuttle refuses the request however it is sent; a screen name means only that screen refuses it, and a direct API call would be accepted.
| Rule | Checked by | What you see |
|---|---|---|
The fields go inside a contract object: {"contract": {... }}. The contract itself is identified by the id in the URL, not in the body | The API | VALIDATION_ERROR: "API body error: missing contract object" |
| A one-off contract cannot be amended | The API | VALIDATION_ERROR: "cannot update non-recurring contracts" |
| A scheduled one-off (fixed amount due later) is not amended by this function: amount and scheduled_date are written to its single open charge instead; Shuttle's own "type does not support scheduled charge edits" rejection is unreachable through the API | The API | VALIDATION_ERROR: "cannot update this contract (no charge found)" when it has no open charge |
next_charge must be in the future (now accepted), and on a cancelled or cancelling contract needs a resume; see Move the next charge | The API | VALIDATION_ERROR: "next_charge must be in the future" / "resume required" |
| On a suspended contract a body with next_charge (or status ACTIVE) is a resume, not an amend, so amount, frequency, charges, event_date, payment_method, legal_entities, modify_in_place, suppress_checks and bypass_validation are refused with it; occurrences, description, alt_key, metadata and keep_active_until are allowed on the resume. A value equal to the contract's current one is dropped rather than refused. See Resume a contract | The API | VALIDATION_ERROR: "Cannot change amount, frequency, modify_in_place on resume" (naming the fields sent) |
| A cancelling contract is not editable, even with bypass_validation | The API | INVALID: "Invalid value for field contract (status PAYMENTCANCELLING is not editable)." (HTTP 400) |
| A completed, cancelled or failed contract is not editable (bypass_validation lifts this only for the internal status change) | The API | INVALID: "Invalid value for field contract (status PAYMENTCOMPLETE is not editable)." with the actual status |
| occurrences cannot be below the number of charges already created; equal is accepted. Merchant View requires at least one more than the charges created and at most 99; the server has no upper bound | The API and Merchant View screen | INVALID: "Invalid value for field numberOfPayments."; Merchant View disables Save |
| frequency must be a known value other than OTHER | The API | INVALID: "Invalid value for field frequency (OTHER is not supported)." / "...frequency (invalid value: X)." |
| frequency cannot be set on a scheduled contract | The API | INVALID: "Invalid value for field frequency (not applicable to ONGOING_SCHEDULED contracts)." |
| amount cannot be set on a recurring variable-amount contract | The API | INVALID: "Invalid value for field amount (not applicable to ONGOING_VARIABLE_AMOUNT contracts)." |
| A change to amount, frequency or occurrences that differs from the current value is a terms change and, without modify_in_place, creates a replacement contract; Merchant View shows the replacement warning and asks for confirmation before sending, and offers the in-place choice only when its option is on | The API and Merchant View screen | replacement created (see Effects); confirmation modal "Editing the amount of a recurring contract, will cancel this contract and issue a new contract." |
| A body with no field the platform recognises is never forwarded | The API | HTTP 200 with the contract re-read and unchanged |
| A concurrent edit or payment run on the same contract: Shuttle waits up to 30 seconds for the contract lock, then fails. Unlike Terminate a contract, this is not translated to LOCKED | The API | INVALID with message "BOLT-7059" (no catalogue text) |
| A replacement that fails after the new contract is created is rolled back: the new contract is cancelled and the original restored to its previous status | The API | INVALID: "(BOLT-7004) Internal Error: Failed to create contract", or the underlying service error |
| Merchant View offers Edit only while the contract is in progress (ACTIVE, PENDING, SUSPENDED, UNRESOLVED, FAILING), recurring or scheduled, and not superseded; the server also accepts SETUP, UNCONFIRMED and ERROR statuses | Merchant View screen | control hidden |
| Merchant View disables amount, frequency and occurrences on a scheduled contract or one with a scheduled date, shows next_charge only while the contract has one, and requires a change before Save; a next_charge change with charges still to come shows the recalculation notice | Merchant View screen | fields disabled, Save disabled |
What happens
- A change to amount, frequency or occurrences replaces the contract: a new contract starts with the new terms, unpaid charges and pending payments move to it, and the original ends CANCELLED with next_contract pointing to the replacement. The response carries the replacement's id and Merchant View opens it. If the replacement cannot be completed, nothing changes and the caller gets the error.
- With modify_in_place, or when only next_charge, keep_active_until or the descriptive fields change, the same contract is updated and returned. Its status is unchanged unless its occurrences cap is now met with nothing outstanding, in which case it moves to COMPLETED (not while keep_active_until is still in the future).
- Webhooks: CONTRACT.UPDATE for an in-place change; CONTRACT.START for the replacement and CONTRACT.COMPLETE for the original when replaced; CONTRACT.COMPLETE whenever a contract completes.
- The next charge is collected on next_charge when supplied, otherwise on the existing date; charges continue until the total reaches occurrences, then the contract completes. A contract with keep_active_until stays active until that date and completes once settled. Charges carried to a replacement are collected under it.
Who can do this
- Roles: Admin only today; intended for Support as well, and being aligned.
- Permission keys (for clients managing permissions directly):
agreement,payment_contractor*on the client or the contract's division, checked by Shuttle through the gateway's permission lookup; a customer session for the contract's account also passes. Shuttle checks the session only. - Admin app: the Edit link and the edit route require
payment_contract; the link is not gated by therecurring_editoption (that option gates Suspend, Resume and Terminate on the same header). The modify-in-place checkbox is enabled only whenrecurring_edit_in_placeis on; off, a terms change always creates a replacement.
Related
Updated 32 minutes ago