Policies
POST
/open/v1/policies/quote
Postman: Quote
Calculate premium from insured amount; does not create a policy. Same pricing as apply: destination-country catalog rate + markup, matched to a price tier.
Quote does not require lineItems, orderPlacedAt, estimatedArrivalDate, buyer, or trackingNumber. It does not enforce the 24-hour order window.
merchantShop and metadata may be included but are ignored until apply.
Request body
| Field | Type | Required | Description |
externalReference | string | Yes | Merchant order ID |
risk.insuredAmount | object | Yes | { amount, currencyCode } |
risk.destination | object | Yes | Destination address |
risk.departure | object | Yes | Origin address |
risk.trackingNumber | string | No | Tracking number (optional at quote) |
risk.carrierCode | string | No | 17track carrier code |
risk.carrierName | string | No | Carrier display name |
Example request
{
"externalReference": "ORD-1001",
"risk": {
"trackingNumber": "1Z999AA10123456784",
"carrierCode": "17",
"carrierName": "UPS",
"insuredAmount": { "amount": "180.00", "currencyCode": "USD" },
"destination": {
"countryCode": "US", "city": "Los Angeles",
"provinceCode": "CA", "addressLine": "123 Main St"
},
"departure": {
"countryCode": "CN", "city": "Shenzhen",
"addressLine": "Factory Zone A"
}
}
}
200 response
{
"externalReference": "ORD-1001",
"insuredAmount": { "amount": "180.00", "currencyCode": "USD" },
"premiumAmount": { "amount": "2.99", "currencyCode": "USD" },
"quoteExpiresInSeconds": 60
}
POST
/open/v1/policies/apply
Postman: Apply
Bind a policy on the Gooval platform after shipment. Returns policyNo immediately. Reinsurance (if the catalog product is not self-retained) is submitted asynchronously and is never exposed to merchants.
Requires Idempotency-Key header.
Key fields
| Field | Required | Description |
externalReference | Yes | Your order ID |
orderPlacedAt | Yes | ISO-8601 order time, e.g. 2026-08-20T09:15:00Z. Must be within the past 24 hours (now − 24h through now). Older orders return 400 order_too_old; future timestamps return 400 order_in_future. |
risk.insuredAmount | Yes | Insured amount |
risk.destination / risk.departure | Yes | Address objects |
risk.trackingNumber | No | Optional until shipment. Add or change later with changeShipment. A policy that already has a tracking number cannot be cancelled. |
risk.estimatedArrivalDate | No | YYYY-MM-DD. Omit at apply and supply later with changeShipment. If omitted when reinsurance is submitted, Gooval defaults to sail date + 14 days. |
risk.lineItems | Yes | Merchandise breakdown (see below) |
buyer.name / buyer.email | Yes | Buyer contact; confirmation email contains spNumber |
attachments | No | Invoice / packing list / product photos (upload first) |
merchantShop | No | Platform-internal shop identity for settlement split (id ≤ 64, name ≤ 200). Gooval still bills the Open tenant as a whole; not sent to the reinsurer. |
metadata | No | Flat string map (max 20 keys; keys [A-Za-z0-9._-], ≤ 40 chars; values ≤ 256 chars) for extra settlement tags. |
Omitted merchantShop / metadata fields are not returned later. Endorsement and cancel do not change them.
Example request
{
"externalReference": "ORD-1002",
"orderPlacedAt": "2026-08-20T09:15:00Z",
"merchantShop": { "id": "shop_8821", "name": "Acme US" },
"metadata": { "channel": "tiktok", "settlementCode": "NA-01" },
"risk": {
"trackingNumber": "1Z999AA10123456784",
"carrierCode": "17",
"carrierName": "UPS",
"insuredAmount": { "amount": "180.00", "currencyCode": "USD" },
"estimatedArrivalDate": "2026-08-25",
"destination": {
"countryCode": "US", "city": "Los Angeles",
"provinceCode": "CA", "addressLine": "123 Main St, Los Angeles, CA"
},
"departure": {
"countryCode": "CN", "city": "Shenzhen",
"addressLine": "Factory Zone A"
},
"lineItems": [{
"itemId": "LINE-001", "sku": "TEE-BLU-M", "name": "Blue Tee",
"category": "apparel",
"quantity": 2,
"unitPrice": { "amount": "50.00", "currencyCode": "USD" },
"imageUrl": "https://cdn.merchant.com/tee.jpg"
}]
},
"buyer": { "name": "Jane Doe", "email": "buyer@example.com" }
}
lineItems rules (required)
- Each line requires
name, category (from reference-data/product-categories), quantity (≥1), unitPrice, and at least one of sku or itemId
- Sum of
unitPrice × quantity must equal risk.insuredAmount (same currency, ±0.01 rounding)
- Optional
imageUrl must be https. Lines are returned in claim lookup as orderLineItems
Omitting lineItems returns 400 field_required. Missing or unknown category returns field_required / invalid_enum. Legacy policies bound before this change still fall back to a single virtual line at claim lookup.
If trackingNumber is omitted at apply, reinsurance waits until a later changeShipment supplies it. GET .../document returns a Gooval application confirmation (not a reinsurer policy).
If externalReference already has an active policy, apply returns 200 with error: duplicate_external_reference — use GET /policies?externalReference= or POST .../cancel before re-applying.
200 success (merchant-visible)
{
"policyNo": "P2026070612345678",
"externalReference": "ORD-1002",
"status": "active",
"premium": { "amount": "2.99", "currencyCode": "USD" },
"insuredAmount": { "amount": "180.00", "currencyCode": "USD" },
"merchantShop": { "id": "shop_8821", "name": "Acme US" },
"metadata": { "channel": "tiktok", "settlementCode": "NA-01" }
}
spNumber is never returned to merchants.
200 duplicate apply
{
"error": "duplicate_external_reference",
"message": "externalReference already has an active policy",
"field": "externalReference"
}
POST
/open/v1/policies/attachments/upload-url
Postman: Attachment upload URL
Get a presigned URL to upload policy attachments before apply.
POST /open/v1/policies/attachments/upload-url with { "contentType", "contentLength", "type"?, "fileName"? }
PUT the file to the returned uploadUrl (include the same Content-Type)
- Pass
attachmentKey on apply
Allowed types: invoice, packingList, productPhoto, other. Files: PDF / JPEG / PNG / WebP, 1 byte–5 MB, max 10 per apply. This route is not 24h-idempotent (presigned URLs expire in 900 seconds).
"attachments": [
{
"attachmentKey": "open-policy-attachments/{storeNumber}/{uuid}.pdf",
"type": "invoice",
"fileName": "invoice.pdf"
}
]
GET /policies/{policyNo} echoes orderPlacedAt and attachments (keys only; no download URL). Attachments are not sent to the reinsurer.
GET
/open/v1/policies
Postman: List policies
Query parameters
| Parameter | Default | Description |
cursor | — | Previous page nextCursor |
limit | 50 | 1–200 |
policyNo | — | Filter by policy number |
externalReference | — | Filter by order ID |
createdFrom / createdTo | — | ISO 8601 time range |
200 response
{
"items": [{
"policyNo": "P2026070612345678",
"externalReference": "ORD-1002",
"status": "active",
"orderName": "#1002",
"premium": { "amount": "2.99", "currencyCode": "USD" },
"createdAt": "2026-07-06T06:00:00+00:00"
}],
"nextCursor": "eyJ..."
}
GET
/open/v1/policies/{policyNo}
Get a single policy including orderPlacedAt, attachment keys (no download URLs), and merchantShop / metadata when set at apply.
GET
/open/v1/policies/{policyNo}/document
Get a pre-signed URL for the Gooval application confirmation PDF (documentType: confirmation). This is not an insurance policy.
Use ?lang=zh or ?lang=en (locale / language also accepted; default en). Reinsurer policy PDFs are not available on the Open API (source=policy returns 400 invalid_document_source).
{ "downloadUrl": "https://...", "expiresIn": 900, "documentType": "confirmation" }
POST
/open/v1/policies/{policyNo}/endorse
Postman: Endorse
Endorse a policy. Gooval applies the change immediately. If the policy is already submitted to a reinsurer, Ping An intelligentEndorse (edrSceneCode: ["900013"]) runs asynchronously and does not fail this request.
Request body
| Field | Required | Description |
endorseScene | Yes | Supported scenes from reference-data/endorse-scenes (see table below) |
changes | Yes | Fields to change |
reason / remark | No | Notes |
endorseScene values
| Code | Mutable fields |
changeDestination | destination, claimPaymentAddress, destinationContinentCategory |
changeShipment | trackingNumber, carrierCode, carrierName, sailDepartureDate, estimatedArrivalDate |
Example request
{
"endorseScene": "changeShipment",
"changes": {
"trackingNumber": "1Z999AA10987654321",
"carrierCode": "17",
"carrierName": "UPS",
"estimatedArrivalDate": "2026-08-28"
},
"reason": "Carrier re-issued tracking label",
"remark": "Original label voided"
}
POST
/open/v1/policies/{policyNo}/cancel
Postman: Cancel
Cancel a policy. Gooval cancels immediately only when the policy has no tracking number. If the policy is already submitted to a reinsurer, Ping An intelligentEndorse (edrSceneCode: ["00006"]) runs asynchronously and does not fail this request.
Request body
| Field | Required | Description |
cancelReasonCode | Yes | From reference-data/cancel-reasons |
cancelReason | No | Reason text |
remark | No | Remark |
{
"cancelReasonCode": "merchantRefund",
"cancelReason": "Order refunded before shipment",
"remark": "Customer cancelled order ORD-1001"
}
200 response status is cancelled.
Returns 409 cancel_blocked_by_shipment when risk.trackingNumber is present, and 409 cancel_blocked_by_claim when the policy has a completed claim or an incomplete claim application.
Postman mapping
| Request name | Method | Path |
| List policies (paginated) | GET | /open/v1/policies |
| List collections | GET | /open/v1/reference-data |
| Get reference data | GET | /open/v1/reference-data/claim-types |
| Cancel reasons | GET | /open/v1/reference-data/cancel-reasons |
| Quote | POST | /open/v1/policies/quote |
| Attachment upload URL | POST | /open/v1/policies/attachments/upload-url |
| Apply | POST | /open/v1/policies/apply |
| Get document | GET | /open/v1/policies/{policyNo}/document |
| Endorse | POST | /open/v1/policies/{policyNo}/endorse |
| Cancel | POST | /open/v1/policies/{policyNo}/cancel |
Environment example
{
"baseUrl": "https://sp-prod.gooval.io",
"OPEN_API_STORE_NUMBER": "100001",
"OPEN_API_SECRET": "<your-secret>"
}