Contracts
External CRUD for contracts, contract services, and optional exception rate cards.
Contracts
Prefix: /api/v1/external/contracts
See Overview for authentication, pagination, and errors. Rate-card payload variants are documented on Services — Rate card write body.
Exception pricing
A contract-service rate card is optional exception pricing for that line. Rating already falls back to the catalog service rate card when no exception exists.
- Omit
rateCards(or send[]) when adding a service — no catalog matrix is copied. - If
priceableAttributesis omitted, catalog dimensions (all types) are copied; prices are not. - GET rate cards with empty
contentmeans no exception. The contract-service header flag is PascalCaseExceptionPricing: false. Nested contractServices[]items still use camelCaseexceptionPricing. - DELETE an exception to return to catalog rates.
Endpoint summary
| Method | Path |
|---|---|
| GET | /contracts |
| GET | /contracts/{contractId} |
| POST | /contracts |
| PUT | /contracts/{contractId} |
| DELETE | /contracts/{contractId} |
| GET | /contract-services/{contractServiceId} |
| PUT | /contract-services/{contractServiceId} |
| PUT | /contract-services/{contractServiceId}/end |
| PUT | /contracts/services/by-instance/{serviceInstanceId}/end |
| GET | /contracts/{contractId}/services |
| GET | /contracts/{contractId}/services/{contractServiceId} |
| POST | /contracts/{contractId}/services |
| PUT | /contracts/{contractId}/services/{contractServiceId} |
| PUT | /contracts/{contractId}/services/{contractServiceId}/end |
| DELETE | /contracts/{contractId}/services/{contractServiceId} |
| GET | /contracts/{contractId}/services/{contractServiceId}/priceable-attributes |
| GET | /contracts/{contractId}/services/{contractServiceId}/priceable-attributes/{attributeId} |
| POST | /contracts/{contractId}/services/{contractServiceId}/priceable-attributes |
| PUT | /contracts/{contractId}/services/{contractServiceId}/priceable-attributes/{attributeId} |
| DELETE | /contracts/{contractId}/services/{contractServiceId}/priceable-attributes/{attributeId} |
| GET | /contracts/{contractId}/services/{contractServiceId}/rate-cards |
| GET | /contracts/{contractId}/services/{contractServiceId}/rate-cards/{currency} |
| GET | /contracts/{contractId}/services/{contractServiceId}/rate-cards/{currency}/versions |
| POST | /contracts/{contractId}/services/{contractServiceId}/rate-cards |
| PUT | /contracts/{contractId}/services/{contractServiceId}/rate-cards/{currency} |
| DELETE | /contracts/{contractId}/services/{contractServiceId}/rate-cards/{currency} |
Contract CRUD
List contracts
GET /api/v1/external/contracts?page=0&size=20&q=acme
Authorization: Bearer YOUR_API_TOKEN| Query | Description |
|---|---|
page | 0-based page index (default 0) |
size | Page size (default 20, max 20) |
q | Optional search on name, number |
Response 200 — ApiResponse wrapping a Spring Page (see Overview).
{
"requestId": "req-1",
"success": true,
"status": 200,
"errors": [],
"data": {
"content": [
{
"__type__": "Contract",
"Id": "3a2f1c5d-9b7e-4c1a-8f36-2d4b6e8a0c11",
"Name": "Acme MSA",
"Number": "C-1001",
"ApprovalStatus": "fe07e71d-cba4-4316-9c7c-4935c3adec34",
"ApprovalStatus__r": { "Name": "Approved" },
"MasterAccountId": "11111111-1111-1111-1111-111111111111"
}
],
"totalElements": 1,
"size": 20,
"number": 0
}
}Get contract
GET /api/v1/external/contracts/{contractId}
Authorization: Bearer YOUR_API_TOKENReturns the PascalCase contract header plus nested Services summaries (camelCase DTO items, including exceptionPricing). Extra MObject fields on the row are also returned. Full attribute and rate-card detail is on GET /contract-services/{id} or the nested service path.
POST /contracts accepts accountExtId when masterAccountId is omitted, templateId on nested services as serviceId, optional dimensionValues / accountMap, and generates number (C-{timestamp}) if omitted.
End a service with PUT /contract-services/{contractServiceId}/end, PUT .../contracts/{contractId}/services/{contractServiceId}/end, or PUT /contracts/services/by-instance/{serviceInstanceId}/end (409 if the instance id is not unique). Body: { "terminationDate": "2026-09-01", "terminationMessage": "..." }. Do not use DELETE to end a service.
Response 200
{
"requestId": "req-1",
"success": true,
"status": 200,
"errors": [],
"data": {
"__type__": "Contract",
"Id": "3a2f1c5d-9b7e-4c1a-8f36-2d4b6e8a0c11",
"Name": "Acme MSA",
"Number": "C-1001",
"ApprovalStatus": "fe07e71d-cba4-4316-9c7c-4935c3adec34",
"ApprovalStatus__r": { "Name": "Approved" },
"MasterAccountId": "11111111-1111-1111-1111-111111111111",
"Services": [
{
"id": "7c9d2e41-5b83-4f6a-9e21-0a5c7d3b8f42",
"serviceId": "e6cbd28a-f34c-482e-8da5-34e21dbd5eb7",
"serviceInstanceId": "SI-1",
"startDate": "2026-01-01",
"endDate": "2026-12-31",
"exceptionPricing": false
}
]
}
}Create contract
POST /api/v1/external/contracts
Content-Type: application/json
Authorization: Bearer YOUR_API_TOKENnumber is optional. If omitted, the API sets C-{timestamp}. If sent, it must be unique (409 otherwise). services is optional.
Request
{
"name": "Acme MSA",
"number": "C-1001",
"startDate": "2026-01-01",
"endDate": "2026-12-31",
"currency": "USD",
"masterAccountId": "11111111-1111-1111-1111-111111111111",
"poNumber": "PO-88213",
"totalPrice": 120000,
"services": [
{
"serviceId": "e6cbd28a-f34c-482e-8da5-34e21dbd5eb7",
"serviceInstanceId": "SI-1",
"startDate": "2026-01-01",
"endDate": "2026-12-31"
}
]
}Response 201 — each nested service without rateCards has exceptionPricing: false.
{
"id": "3a2f1c5d-9b7e-4c1a-8f36-2d4b6e8a0c11",
"name": "Acme MSA",
"number": "C-1001",
"startDate": "2026-01-01",
"endDate": "2026-12-31",
"currency": "USD",
"masterAccountId": "11111111-1111-1111-1111-111111111111",
"poNumber": "PO-88213",
"totalPrice": 120000,
"services": [
{
"id": "7c9d2e41-5b83-4f6a-9e21-0a5c7d3b8f42",
"serviceId": "e6cbd28a-f34c-482e-8da5-34e21dbd5eb7",
"serviceInstanceId": "SI-1",
"startDate": "2026-01-01",
"endDate": "2026-12-31",
"exceptionPricing": false
}
]
}Update contract
PUT /api/v1/external/contracts/{contractId}
Content-Type: application/json
Authorization: Bearer YOUR_API_TOKENUpdates scalars. If services is present, existing contract services are replaced. Omit services to leave lines unchanged.
Request
{
"name": "Acme MSA (2026 renewal)",
"endDate": "2027-12-31",
"totalPrice": 145000
}Response 200 — camelCase ContractPayload, including nested services summaries. Not wrapped in ApiResponse.
Delete contract
DELETE /api/v1/external/contracts/{contractId}
Authorization: Bearer YOUR_API_TOKENResponse 204 — no body.
Response 409 if the contract still has services.
{
"status": 409,
"error": "Conflict",
"message": "Contract still has services",
"path": "uri=/api/v1/external/contracts/3a2f1c5d-9b7e-4c1a-8f36-2d4b6e8a0c11"
}Contract services
List contract services
GET /api/v1/external/contracts/{contractId}/services?page=0&size=20
Authorization: Bearer YOUR_API_TOKENResponse 200
{
"requestId": "req-1",
"success": true,
"status": 200,
"errors": [],
"data": {
"content": [
{
"__type__": "ContractServices",
"Id": "7c9d2e41-5b83-4f6a-9e21-0a5c7d3b8f42",
"ServiceId": "e6cbd28a-f34c-482e-8da5-34e21dbd5eb7",
"ServiceId__r": { "Name": "gpt-4o" },
"ServiceInstanceId": "SI-1",
"StartDate": "2026-01-01",
"EndDate": "2026-12-31",
"ChargeFrequency": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
"ChargeFrequency__r": { "Name": "Monthly" },
"BillingFrequency": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
"BillingFrequency__r": { "Name": "Monthly" },
"ExceptionPricing": true
}
],
"totalElements": 1,
"size": 20,
"number": 0
}
}Get contract service
GET /api/v1/external/contracts/{contractId}/services/{contractServiceId}
Authorization: Bearer YOUR_API_TOKENReturns the contract-service header (ApiResponse + PascalCase __r row) plus ExceptionPricing, PriceableAttributes, and RateCards. RateCards is empty and ExceptionPricing is false when there is no exception (rating uses catalog list price). Nested attribute and rate-card objects keep the same camelCase DTO shape as create/update and the nested CRUD routes.
GET /api/v1/external/contract-services/{contractServiceId} returns the same payload.
Response 200
{
"requestId": "req-1",
"success": true,
"status": 200,
"errors": [],
"data": {
"__type__": "ContractServices",
"Id": "7c9d2e41-5b83-4f6a-9e21-0a5c7d3b8f42",
"ServiceId": "e6cbd28a-f34c-482e-8da5-34e21dbd5eb7",
"ServiceId__r": { "Name": "gpt-4o" },
"ServiceInstanceId": "SI-1",
"StartDate": "2026-01-01",
"EndDate": "2026-12-31",
"ChargeFrequency": "afab5030-69c2-4407-b7f3-826f0af798b2",
"ChargeFrequency__r": { "Name": "Monthly" },
"BillingFrequency": "afab5030-69c2-4407-b7f3-826f0af798b2",
"BillingFrequency__r": { "Name": "Monthly" },
"ExceptionPricing": true,
"PriceableAttributes": [
{
"id": "5c1e7a29-3f8b-4d62-90ac-4b6d2f8e1c53",
"name": "Input_Tokens",
"label": "Input Tokens",
"dimensionDataType": "Number",
"dimValues": []
}
],
"RateCards": [
{
"currency": "USD",
"versionNo": 1,
"isCurrent": true,
"fromDate": "2026-01-01",
"rows": [
{
"id": "6a3c9e17-8d2b-4f50-a91e-7c5b0d4f2a68",
"priceDimId": "5c1e7a29-3f8b-4d62-90ac-4b6d2f8e1c53",
"label": "Negotiated input token rate",
"priceMethod": "QUANTITY",
"unitRate": 0.0018,
"attributeValues": []
}
]
}
]
}
}Add a service to a contract
POST /api/v1/external/contracts/{contractId}/services
Content-Type: application/json
Authorization: Bearer YOUR_API_TOKENserviceId is required. Omit priceableAttributes to copy catalog dimensions; omit rateCards to keep catalog list prices.
Request — attach at catalog pricing.
{
"serviceId": "e6cbd28a-f34c-482e-8da5-34e21dbd5eb7",
"serviceInstanceId": "SI-1",
"startDate": "2026-01-01",
"endDate": "2026-12-31",
"chargeFrequency": "Monthly",
"billingFrequency": "Monthly"
}Request — attach with exception pricing in the same call.
{
"serviceId": "e6cbd28a-f34c-482e-8da5-34e21dbd5eb7",
"serviceInstanceId": "SI-2",
"startDate": "2026-01-01",
"endDate": "2026-12-31",
"rateCards": [
{
"currency": "USD",
"rows": [
{
"priceMethod": "QUANTITY",
"priceDimName": "Input_Tokens",
"label": "Negotiated input token rate",
"unitRate": 0.0018,
"attributeValues": []
}
]
}
]
}Response 201 — camelCase ContractServicePayload with nested priceableAttributes and rateCards. Not wrapped in ApiResponse.
Update contract service
PUT /api/v1/external/contracts/{contractId}/services/{contractServiceId}
Content-Type: application/json
Authorization: Bearer YOUR_API_TOKENRequest
{
"endDate": "2027-06-30",
"billingFrequency": "Quarterly"
}Response 200 — camelCase ContractServicePayload, same shape as create.
The same update (no contract id in the path):
PUT /api/v1/external/contract-services/{contractServiceId}
Content-Type: application/json
Authorization: Bearer YOUR_API_TOKENEnd contract service
PUT /api/v1/external/contract-services/{contractServiceId}/end
PUT /api/v1/external/contracts/{contractId}/services/{contractServiceId}/end
Content-Type: application/json
Authorization: Bearer YOUR_API_TOKENRequest
{
"terminationDate": "2026-09-01",
"terminationMessage": "Offboarded"
}terminationDate is required (400 "Termination date is required" if omitted). terminationMessage is optional.
Response 200 — same ApiResponse + PascalCase contract-service row as GET, including ExceptionPricing, PriceableAttributes, and RateCards.
{
"requestId": "req-1",
"success": true,
"status": 200,
"errors": [],
"data": {
"__type__": "ContractServices",
"Id": "7c9d2e41-5b83-4f6a-9e21-0a5c7d3b8f42",
"ServiceId": "e6cbd28a-f34c-482e-8da5-34e21dbd5eb7",
"ServiceId__r": { "Name": "gpt-4o" },
"ServiceInstanceId": "SI-1",
"StartDate": "2026-01-01",
"EndDate": "2026-09-01",
"ChargeFrequency": "afab5030-69c2-4407-b7f3-826f0af798b2",
"ChargeFrequency__r": { "Name": "Monthly" },
"BillingFrequency": "afab5030-69c2-4407-b7f3-826f0af798b2",
"BillingFrequency__r": { "Name": "Monthly" },
"ExceptionPricing": true,
"PriceableAttributes": [
{
"id": "5c1e7a29-3f8b-4d62-90ac-4b6d2f8e1c53",
"name": "Input_Tokens",
"label": "Input Tokens",
"dimensionDataType": "Number",
"dimValues": []
}
],
"RateCards": [
{
"currency": "USD",
"versionNo": 1,
"isCurrent": true,
"fromDate": "2026-01-01",
"rows": [
{
"id": "6a3c9e17-8d2b-4f50-a91e-7c5b0d4f2a68",
"priceDimId": "5c1e7a29-3f8b-4d62-90ac-4b6d2f8e1c53",
"label": "Negotiated input token rate",
"priceMethod": "QUANTITY",
"unitRate": 0.0018,
"attributeValues": []
}
]
}
]
}
}Response 400 when terminationDate is omitted.
{
"status": 400,
"error": "Bad Request",
"message": "Termination date is required",
"path": "uri=/api/v1/external/contract-services/7c9d2e41-5b83-4f6a-9e21-0a5c7d3b8f42/end"
}Delete contract service
DELETE /api/v1/external/contracts/{contractId}/services/{contractServiceId}
Authorization: Bearer YOUR_API_TOKENResponse 204 — no body.
Priceable attributes (contract service)
Same body shape as catalog service attributes, plus the optional dimensionValText field.
List contract-service priceable attributes
GET /api/v1/external/contracts/{contractId}/services/{contractServiceId}/priceable-attributes?page=0&size=20
Authorization: Bearer YOUR_API_TOKENResponse 200
{
"requestId": "req-1",
"success": true,
"status": 200,
"errors": [],
"data": {
"content": [
{
"__type__": "ContractDimension",
"Id": "5c1e7a29-3f8b-4d62-90ac-4b6d2f8e1c53",
"Name": "Input_Tokens",
"Label": "Input Tokens",
"IsContractInput": true
}
],
"totalElements": 1,
"size": 20,
"number": 0
}
}Get contract-service priceable attribute
GET /api/v1/external/contracts/{contractId}/services/{contractServiceId}/priceable-attributes/{attributeId}
Authorization: Bearer YOUR_API_TOKENResponse 200
{
"requestId": "req-1",
"success": true,
"status": 200,
"errors": [],
"data": {
"__type__": "ContractDimension",
"Id": "5c1e7a29-3f8b-4d62-90ac-4b6d2f8e1c53",
"Name": "Input_Tokens",
"Label": "Input Tokens",
"IsContractInput": true,
"DimensionValText": "1000"
}
}Create contract-service priceable attribute
POST /api/v1/external/contracts/{contractId}/services/{contractServiceId}/priceable-attributes
Content-Type: application/json
Authorization: Bearer YOUR_API_TOKENRequest
{
"name": "Committed_Tokens",
"label": "Committed Tokens",
"dimensionDataType": "Number",
"isContractInput": true,
"dimensionValText": "5000000"
}Response 201
{
"id": "8b4f0d36-9a1c-4e75-b2d8-6f3a5c9e0b21",
"name": "Committed_Tokens",
"label": "Committed Tokens",
"dimensionDataType": "Number",
"isContractInput": true,
"dimensionValText": "5000000",
"dimValues": []
}Update contract-service priceable attribute
PUT /api/v1/external/contracts/{contractId}/services/{contractServiceId}/priceable-attributes/{attributeId}
Content-Type: application/json
Authorization: Bearer YOUR_API_TOKENRequest
{
"name": "Committed_Tokens",
"label": "Committed Tokens (revised)",
"dimensionDataType": "Number",
"dimensionValText": "7500000"
}Response 200
{
"id": "8b4f0d36-9a1c-4e75-b2d8-6f3a5c9e0b21",
"name": "Committed_Tokens",
"label": "Committed Tokens (revised)",
"dimensionDataType": "Number",
"dimensionValText": "7500000",
"dimValues": []
}Delete contract-service priceable attribute
DELETE /api/v1/external/contracts/{contractId}/services/{contractServiceId}/priceable-attributes/{attributeId}
Authorization: Bearer YOUR_API_TOKENResponse 204 — no body.
Response 409 if a current exception rate-card row references the attribute.
Exception rate cards
Same body shape as catalog rate cards. See Rate card write body for every priceMethod variant.
List exception rate cards
GET /api/v1/external/contracts/{contractId}/services/{contractServiceId}/rate-cards?page=0&size=20
Authorization: Bearer YOUR_API_TOKENAn empty content array means there is no exception and rating uses catalog list price.
Response 200
{
"content": [
{
"currency": "USD",
"versionNo": 1,
"isCurrent": true,
"fromDate": "2026-01-01",
"rows": [
{
"id": "6a3c9e17-8d2b-4f50-a91e-7c5b0d4f2a68",
"priceDimId": "5c1e7a29-3f8b-4d62-90ac-4b6d2f8e1c53",
"label": "Contract override",
"priceMethod": "FLAT_RATE",
"flatRate": 75.0,
"attributeValues": []
}
]
}
],
"page": 0,
"size": 20,
"totalElements": 1,
"totalPages": 1
}Get exception rate card by currency
GET /api/v1/external/contracts/{contractId}/services/{contractServiceId}/rate-cards/USD
Authorization: Bearer YOUR_API_TOKENResponse 200
{
"currency": "USD",
"versionNo": 1,
"isCurrent": true,
"fromDate": "2026-01-01",
"rows": [
{
"id": "6a3c9e17-8d2b-4f50-a91e-7c5b0d4f2a68",
"priceDimId": "5c1e7a29-3f8b-4d62-90ac-4b6d2f8e1c53",
"label": "Contract override",
"priceMethod": "FLAT_RATE",
"flatRate": 75.0,
"attributeValues": []
}
]
}Returns 404 when there is no exception for that currency.
List exception rate-card versions
GET /api/v1/external/contracts/{contractId}/services/{contractServiceId}/rate-cards/USD/versions?page=0&size=20
Authorization: Bearer YOUR_API_TOKENResponse 200
{
"content": [
{
"currency": "USD",
"versionNo": 2,
"isCurrent": true,
"fromDate": "2026-07-01",
"rows": [
{ "priceDimId": "5c1e7a29-3f8b-4d62-90ac-4b6d2f8e1c53", "label": "Contract override", "priceMethod": "FLAT_RATE", "flatRate": 70.0, "attributeValues": [] }
]
},
{
"currency": "USD",
"versionNo": 1,
"isCurrent": false,
"fromDate": "2026-01-01",
"toDate": "2026-06-30",
"rows": [
{ "priceDimId": "5c1e7a29-3f8b-4d62-90ac-4b6d2f8e1c53", "label": "Contract override", "priceMethod": "FLAT_RATE", "flatRate": 75.0, "attributeValues": [] }
]
}
],
"page": 0,
"size": 20,
"totalElements": 2,
"totalPages": 1
}Set exception pricing
POST /api/v1/external/contracts/{contractId}/services/{contractServiceId}/rate-cards
Content-Type: application/json
Authorization: Bearer YOUR_API_TOKENReturns 409 if that currency already has an exception card.
Request
{
"currency": "USD",
"rows": [
{
"priceMethod": "FLAT_RATE",
"priceDimName": "Usage",
"label": "Contract override",
"flatRate": 75.0,
"attributeValues": []
}
]
}curl -sS -X POST \
"https://YOUR_TENANT_HOST/api/v1/external/contracts/3a2f1c5d-9b7e-4c1a-8f36-2d4b6e8a0c11/services/7c9d2e41-5b83-4f6a-9e21-0a5c7d3b8f42/rate-cards" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"currency": "USD",
"rows": [{
"priceMethod": "FLAT_RATE",
"priceDimName": "Usage",
"label": "Contract override",
"flatRate": 75.0,
"attributeValues": []
}]
}'Response 201
{
"currency": "USD",
"versionNo": 1,
"isCurrent": true,
"fromDate": "2026-08-31",
"rows": [
{
"id": "6a3c9e17-8d2b-4f50-a91e-7c5b0d4f2a68",
"priceDimId": "5c1e7a29-3f8b-4d62-90ac-4b6d2f8e1c53",
"label": "Contract override",
"priceMethod": "FLAT_RATE",
"flatRate": 75.0,
"attributeValues": []
}
]
}Update exception pricing
PUT /api/v1/external/contracts/{contractId}/services/{contractServiceId}/rate-cards/USD
Content-Type: application/json
Authorization: Bearer YOUR_API_TOKENThe currency in the body must match the path segment.
Request
{
"currency": "USD",
"rows": [
{
"priceMethod": "FLAT_RATE",
"priceDimName": "Usage",
"label": "Contract override",
"flatRate": 70.0,
"attributeValues": []
}
]
}Response 200
{
"currency": "USD",
"versionNo": 2,
"isCurrent": true,
"fromDate": "2026-08-31",
"rows": [
{
"id": "9c5e1a48-0b7d-4386-c3f1-8a2d6e0b4f79",
"priceDimId": "5c1e7a29-3f8b-4d62-90ac-4b6d2f8e1c53",
"label": "Contract override",
"priceMethod": "FLAT_RATE",
"flatRate": 70.0,
"attributeValues": []
}
]
}Remove exception pricing
DELETE /api/v1/external/contracts/{contractId}/services/{contractServiceId}/rate-cards/USD
Authorization: Bearer YOUR_API_TOKENDrops the exception; rating falls back to the catalog service rate card.
Response 204 — no body.