Open API

Gooval REST API for self-hosted merchants. Base path: /open/v1

Overview

ItemDescription
Base URLhttps://{api-host}/open/v1
Content-Typeapplication/json
EncodingUTF-8
Field namingcamelCase
TimestampsISO 8601 UTC

Environments

EnvironmentBase URL
Productionhttps://sp-prod.gooval.io/open/v1
Developmenthttps://sp-test.gooval.io/open/v1

Set Postman variable {{baseUrl}} to the host root (without /open/v1), e.g. https://sp-prod.gooval.io.

Authentication

Every request must include HMAC signature headers. Never call the Open API from a browser or frontend — keep the API secret on your backend only.

Auth header names remain X-Gwofy-* (legacy naming, matches the current Gooval backend — send headers exactly as listed below).

Credentials

Before integrating, obtain storeNumber (store ID) and apiSecret (API secret). Contact Gooval to enable access:

After onboarding, Gooval provides storeNumber and apiSecret (shown once — store immediately on your server; never commit to git or expose in the client).

CredentialUsagePostman / Apifox variable
storeNumberHeader X-Gwofy-Store-NumberOPEN_API_STORE_NUMBER
apiSecretHMAC signing (not sent in headers)OPEN_API_SECRET

Headers

HeaderRequiredDescription
X-Gwofy-Store-NumberYesTenant store number
X-Gwofy-TimestampYesUnix timestamp (seconds), ±5 minutes
X-Gwofy-SignatureYesHMAC-SHA256 hex signature
Content-TypeYesapplication/json
Idempotency-KeyWrite opsIdempotency key; same key within 24h returns the same response

Signature

message = storeNumber + "\\\\\\\\
" + timestamp + "\\\\\\\\
" + rawBody
signature = hex(hmac_sha256(apiSecret, message))
  • rawBody: raw HTTP request body string; empty string "" for GET
  • Signature is lowercase hexadecimal

Environment variables

VariableDescription
OPEN_API_STORE_NUMBERStore number
OPEN_API_SECRETAPI secret (server-side only)

Rate limits

Default per-store limits (60-second sliding window):

BucketRoutesDefault limit
quotesPOST /policies/quote6000/min
policies_writeapply / endorse / cancel30/min
attachmentsPOST /policies/attachments/upload-url120/min
policies_readGET policies, events, document120/min
reference_dataGET reference-data300/min

When exceeded, returns 429 with Retry-After header. Cache quote results server-side (30–60s) for checkout traffic.

Error format

{
  "error": "error_code",
  "message": "Human-readable message",
  "field": "risk.insuredAmount"
}

Responses use a stable machine error code and a human-readable message. Optional field names the JSON path when validation fails.

HTTPerrorDescription
401missing_auth_headersMissing auth headers
401invalid_signatureInvalid signature
401timestamp_expiredTimestamp expired
429rate_limit_exceededRate limited
404policy_not_foundPolicy not found
400field_requiredMissing schema field
400line_items_total_mismatchSum of lineItems amounts does not equal insuredAmount
400invalid_urlimageUrl is not a valid https URL
400invalid_emailbuyer.email is missing @ / domain, or otherwise invalid
400invalid_datetimeorderPlacedAt or estimatedArrivalDate is not a valid timestamp/date
400invalid_attachment_keyattachmentKey is missing or belongs to another store
400too_many_attachmentsMore than 10 attachments
400invalid_content_typeUpload file type is not pdf/jpeg/png/webp
400invalid_content_lengthUpload size is missing or exceeds 5 MB
400currency_mismatchAmount currency does not match shop settlement currency
400shop_currency_not_configuredOpen tenant missing shopCurrencyCode
400country_not_supportedDestination country is not in the assigned protection catalog
400coverage_out_of_rangeinsuredAmount is below min or above max coverage
400premium_exceeds_maxinsuredAmount × country rate exceeds the highest price tier
400invalid_enumUnknown reference-data code
400invalid_document_sourcesource is policy / insurer / reinsurer — Open API only issues confirmation PDFs
400invalid_document_langlang is not zh or en
200duplicate_external_referenceexternalReference already has an active policy
502policy_apply_failedInsurer rejected apply (mapped)

Core flow

1. GET /reference-data/{collection} → load enums (countries, carriers, product-categories, endorse-scenes, …) 2. POST /policies/quote → checkout premium (optional; cache 30–60s) 3. POST /policies/apply → bind policy after shipment → policyNo + externalReference 4. Gooval emails buyer with spNumber for /claim/* lookup (spNumber never returned to merchants) 5. POST /policies/{policyNo}/endorse or /cancel (optional) 6. GET /policies / /events → poll or use outbound webhooks

Premium uses the same formula as the Shopify storefront: insured amount × destination-country rate (catalog rate + merchant markup), matched to the cheapest protection price tier at or above that fee. Quote uses the same pricing as apply.

Reference data

GET /open/v1/reference-data Postman: List collections

List all reference-data collection names.

200 response

{
  "schemaVersion": "2026.07.1",
  "collections": [
    "countries", "currencies", "carriers", "product-categories",
    "destination-continent-categories", "goods-value-categories",
    "certificate-types", "endorse-scenes", "cancel-reasons",
    "claim-types", "policy-statuses", "claim-customer-statuses"
  ]
}
GET /open/v1/reference-data/{collection} Postman: Get reference data

Get enum items for a collection. Supports If-None-Match; returns 304 when unchanged.

Path parameters

ParameterDescription
collectione.g. product-categories, cancel-reasons, endorse-scenes, claim-types

200 example — cancel-reasons

{
  "schemaVersion": "2026.07.1",
  "collection": "cancel-reasons",
  "items": [
    { "code": "merchantRefund", "name": "Merchant refund before shipment" },
    { "code": "duplicatePolicy", "name": "Duplicate policy" },
    { "code": "customerWithdraw", "name": "Customer withdrawal" },
    { "code": "other", "name": "Other" }
  ]
}

Policies

POST /open/v1/policies/quote Postman: Quote

Calculate premium from insured amount; does not create a policy. Same pricing as apply.

Quote does not require lineItems, orderPlacedAt, estimatedArrivalDate, or buyer.

Request body

FieldTypeRequiredDescription
externalReferencestringYesMerchant order ID
risk.trackingNumberstringYesTracking number
risk.insuredAmountobjectYes{ amount, currencyCode }
risk.destinationobjectYesDestination address
risk.departureobjectYesOrigin address
risk.carrierCodestringNo17track carrier code

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 never exposed to merchants.

Requires Idempotency-Key header.

Key fields

FieldRequiredDescription
externalReferenceYesYour order ID
orderPlacedAtYesISO-8601 order time, e.g. 2026-08-20T09:15:00Z
risk.trackingNumberYesTracking number
risk.insuredAmountYesInsured amount
risk.destination / risk.departureYesAddress objects
risk.estimatedArrivalDateYesYYYY-MM-DD
risk.lineItemsYesMerchandise breakdown (see below)
buyer.name / buyer.emailYesBuyer contact; confirmation email contains spNumber
attachmentsNoInvoice / packing list / product photos (upload first)
orderNameNoDisplay order name

Example request

{
  "externalReference": "ORD-1002",
  "orderPlacedAt": "2026-08-20T09:15:00Z",
  "orderName": "#1002",
  "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": "alice@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 (±0.01)
  • Optional imageUrl must be https

Omitting lineItems returns 400 field_required. Missing or unknown category returns field_required / invalid_enum. Legacy policies without line items fall back to a single virtual line at claim lookup.

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" }
}

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.

  1. POST /open/v1/policies/attachments/upload-url with { "contentType", "contentLength", "type"?, "fileName"? }
  2. PUT the file to the returned uploadUrl (include the same Content-Type)
  3. 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

ParameterDefaultDescription
cursorPrevious page nextCursor
limit501–200
policyNoFilter by policy number
externalReferenceFilter by order ID
createdFrom / createdToISO 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 and attachment keys.

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

Request body

FieldRequiredDescription
endorseSceneYesSupported scenes from reference-data/endorse-scenes (see table below)
changesYesFields to change
reason / remarkNoNotes

endorseScene values

CodeMutable fields
changeDestinationdestination, claimPaymentAddress, destinationContinentCategory
changeShipmenttrackingNumber, carrierCode, sailDepartureDate, estimatedArrivalDate

Example request

{
  "endorseScene": "changeShipment",
  "changes": {
    "trackingNumber": "1Z999AA10987654321",
    "carrierCode": "17"
  },
  "reason": "Carrier re-issued tracking label",
  "remark": "Original label voided"
}
POST /open/v1/policies/{policyNo}/cancel Postman: Cancel

Request body

FieldRequiredDescription
cancelReasonCodeYesSee cancel-reasons
cancelReasonNoReason text
remarkNoRemark
{
  "cancelReasonCode": "merchantRefund",
  "cancelReason": "Order refunded before shipment",
  "remark": "Customer cancelled order ORD-1001"
}

200 response status is cancelled.

Events (polling)

GET /open/v1/events

Query parameters

ParameterDescription
sinceISO 8601; events after this time only
type / eventFilter by event type
limitDefault 50

Event types: policy.insured · policy.failed · policy.endorsed · policy.cancelled · claim.submitted · claim.status_changed · claim.completed

Outbound webhooks

Register your callback URL in the Merchant portal (seller.gooval.io), or contact contact@gooval.io.

Gooval POSTs JSON events to your URL. Payloads include policyNo and externalReferencenever spNumber.

Webhook signature header remains X-Gwofy-Signature (legacy naming, matches backend).

Subscribed events

webhook.test · policy.insured · policy.failed · policy.endorsed · policy.cancelled · claim.submitted · claim.status_changed · claim.completed

When subscribedEvents is omitted, all event types above are delivered.

Verification

X-Gwofy-Signature = hex(hmac_sha256(webhook_secret, timestamp + "." + rawBody))

Postman mapping

Request nameMethodPath
List policies (paginated)GET/open/v1/policies
List collectionsGET/open/v1/reference-data
Get reference dataGET/open/v1/reference-data/claim-types
Cancel reasonsGET/open/v1/reference-data/cancel-reasons
QuotePOST/open/v1/policies/quote
Attachment upload URLPOST/open/v1/policies/attachments/upload-url
ApplyPOST/open/v1/policies/apply
Get documentGET/open/v1/policies/{policyNo}/document
EndorsePOST/open/v1/policies/{policyNo}/endorse
CancelPOST/open/v1/policies/{policyNo}/cancel

Environment example

{
  "baseUrl": "https://sp-prod.gooval.io",
  "OPEN_API_STORE_NUMBER": "100001",
  "OPEN_API_SECRET": "<your-secret>"
}