Create
Create a refund for a closed invoice with accepted funds.
Creates a merchant-funded refund for a paid, overpaid, underpaid, expired, or invalid invoice with accepted funds to a supplied destination in the invoice's currency and network. The requested amount is capped by accepted gross payments minus completed and in-flight refund principal. The merchant debit and recipient amount follow the snapshotted refund and configured network fee allocations. Actual on-chain network cost is reported separately and is not added to the merchant debit. The refund has its own on-chain execution; no separate payout is created.
Usage notes#
Requires refunds:write. A closed invoice with accepted funds is refundable (paid, overpaid, underpaid, expired, or invalid). Use the invoice's currency/network pair and a recipient-confirmed return address. Partial refunds are allowed within the accepted-gross remainder after completed and in-flight refund principals.
Save the refund ID. Its payout_id equals that ID and does not refer to a separate payout resource. recipient_amount and merchant_debit_amount already reflect commercial fee allocation; do not add measured network cost again.
If this request is refused, a corrected body may reuse the key only when Idempotency-Key-Released: true confirms release. See refunds and idempotency.
An invoice with a confirmed full refund request cannot accept a separate custom-address refund through this operation. A reversed refund or an uncertain earlier execution also blocks a replacement until reconciled. Use the invoice refund history in the dashboard for manual full refunds.
/v1/refundscurl --request POST 'https://api.thehalfin.com/v1/refunds' \
--header "X-API-Key: $HALFIN_API_KEY" \
--header 'Idempotency-Key: createRefund-example-1001' \
--header 'Content-Type: application/json' \
--data '{
"invoice_id": "00000000-0000-0000-0000-000000000003",
"currency": "USDC",
"network": "ethereum",
"amount": "5.00",
"destination": "0x0000000000000000000000000000000000000001",
"description": "Partial refund for order 1001"
}'Authorization
X-API-KeystringRequiredAPI key issued by the merchant dashboard. The key's environment (live/test) determines which data the caller can access. Permissions on the key control which endpoints are reachable.Example:
$HALFIN_API_KEY
Header Parameters
Idempotency-KeystringRequiredPersist a unique key before sending this operation. Retry an uncertain outcome with the same key and unchanged request; a new operation needs a new key.
See idempotency and retries for replay, conflict, and key-release behavior.
Full header contract
Required for merchant POST requests that create invoices, addresses, payouts, or refunds. Use a unique key for each intended operation. The same key namespace is shared by all merchant operations within the same merchant and environment. Retry the original request with the same key and unchanged path, query, and body. JSON key ordering and insignificant whitespace are ignored; numeric literals and string values are preserved ("10" differs from "10.00"). Changing any field under a bound key returns 409 conflict. An executing request returns 409 in_flight. An exact retry returns the permanent original outcome with Idempotent-Replayed: true. Only Idempotency-Key-Released: true confirms that a refused attempt released the key. An unconfirmed release returns 503 service_unavailable without that header. Under uncertainty, retry the original request with the same key. The key is never echoed in response bodies. Keys contain 1–255 printable ASCII characters without spaces; a UUID is recommended.
- Minimum length: 1
- Maximum length: 255
- Pattern: ^[\x21-\x7E]+$
Example:
createRefund-example-1001
Request Body
Required
application/json
invoice_idstringRequiredInvoice UUID whose accepted payments bound the refund amount.- Format: uuid
Example:
00000000-0000-0000-0000-000000000001currencystringRequiredCredited asset currency to refund.- Minimum length: 1
- Maximum length: 20
Example:
stringnetworkstringRequiredCredited asset network. Required together with currency; an unknown pair returns 422 gate_unknown.- Minimum length: 1
- Maximum length: 64
Example:
stringamountstringRequiredDecimal string amount to refund.- Maximum length: 50
Example:
1destinationstringRequiredCustomer-supplied destination address.- Maximum length: 256
Example:
stringdestination_tagstringOptionalMemo/tag for chains that require it.- Maximum length: 256
Example:
stringdescriptionstringOptionalHuman-readable refund description.- Maximum length: 1000
Example:
stringmetadataobjectOptionalMerchant key-value metadata; omitted invoice metadata is stored and returned as an empty object.- Maximum properties: 50
Example:
{"key":"string"}[key]stringOptional- Maximum length: 500
Example:
string
Response Body
201 Refund created.
Idempotent-Replayed"true"OptionalPresent and true when returning the stored outcome of an earlier execution.Example:
trueIdempotency-Key-Released"true"OptionalPresent and true after a proven no-effect refusal and confirmed release. This attempt bound nothing; a concurrent request may acquire the key.Example:
true
application/json
dataobjectRequired- Unknown properties are rejected
Example:
{"id":"00000000-0000-0000-0000-000000000004","invoice_id":"00000000-0000-0000-0000-000000000003","payout_id":"00000000-0000-0000-0000-000000000004","currency":"USDC","network":"ethereum","amount":"5.00","recipient_amount":"5.00","merchant_debit_amount":"5.10","refund_fee":"0.10","refund_fee_allocation":"merchant","network_fee":null,"configured_network_fee_amount":"0.00","network_fee_allocation":"platform","destination":"0x0000000000000000000000000000000000000001","destination_tag":null,"status":"pending","environment":"live","tx_hash":null,"failure_reason":null,"description":"Partial refund for order 1001","metadata":null,"completed_at":null,"created_at":"2026-09-14T10:00:00Z","payment_id":null,"refund_request_id":null}idstringRequiredOpaque refund identifier. Save and pass it unchanged to GET refund.- Maximum length: 80
Example:
stringinvoice_idstringRequiredInvoice UUID being refunded.- Format: uuid
Example:
00000000-0000-0000-0000-000000000001payment_idstring | nullRequiredOriginal incoming payment. Null for invoice-level API refunds without payment attribution.- Format: uuid
Example:
nullrefund_request_idstring | nullRequiredGroups refunds authorized by one manual confirmation. Null for individually created API refunds.- Format: uuid
Example:
nullpayout_idstringRequiredCompatibility identifier equal to this refund's ID. It does not identify a separate payout.- Maximum length: 80
Example:
stringcurrencystringRequiredRefunded asset currency.Example:
stringnetworkstringRequiredRefunded asset network.Example:
stringamountstringRequiredBase/requested refund amount as a decimal string.Example:
1recipient_amountstringRequiredAmount sent to the customer after customer-allocated commercial charges.- Format: decimal
- Pattern: ^\d+(\.\d+)?$
Example:
1merchant_debit_amountstringRequiredAuthorized merchant debit including merchant-paid charges. For a payment not credited to the merchant, its held funds supply principal and this amount covers charges only. Failed or reversed execution can release or reverse the debit.- Format: decimal
- Pattern: ^\d+(\.\d+)?$
Example:
1refund_feestringRequiredDecimal string platform refund fee in refund currency.Example:
1refund_fee_allocation"customer" | "merchant" | "platform"RequiredExample:
customernetwork_feestring | nullRequiredDecimal string actual on-chain network fee when available; null when unavailable, including returns of funds that were not credited to the merchant balance.Example:
nullconfigured_network_fee_amountstringRequiredSnapshotted commercial network charge; distinct from actual on-chain cost.- Format: decimal
- Pattern: ^\d+(\.\d+)?$
Example:
1network_fee_allocation"customer" | "merchant" | "platform"RequiredExample:
customerdestinationstringRequiredDestination blockchain address.Example:
stringdestination_tagstring | nullRequiredMemo/tag if applicable.Example:
nullstatus"pending" | "processing" | "confirming" | "completed" | "failed" | "reversed"RequiredRefund execution status.Example:
pendingenvironment"live" | "test"RequiredEnvironment this refund belongs to.Example:
livetx_hashstring | nullRequiredOn-chain transaction hash after completion.Example:
nullfailure_reasonstring | nullRequiredMerchant-facing reason when the refund fails. A rejected destination is reported as destination_rejected; other failures are reported as internal_error. Null while processing or after completion.Example:
nulldescriptionstring | nullRequiredRefund description.Example:
nullmetadataobject | nullRequiredMerchant-supplied metadata.Example:
null[key]stringOptionalExample:
string
completed_atstring | nullRequiredCompletion timestamp.- Format: date-time
Example:
nullcreated_atstringRequiredCreation timestamp.- Format: date-time
Example:
2026-01-01T00:00:00Z
metaobjectRequired- Unknown properties are rejected
Example:
{"request_id":"00000000-0000-0000-0000-000000000001"}request_idstringRequiredCorrelation identifier issued by the API, also returned in the X-Request-ID response header. Client-supplied X-Request-ID values are replaced.Example:
string
{
"data": {
"id": "00000000-0000-0000-0000-000000000004",
"invoice_id": "00000000-0000-0000-0000-000000000003",
"payout_id": "00000000-0000-0000-0000-000000000004",
"currency": "USDC",
"network": "ethereum",
"amount": "5.00",
"recipient_amount": "5.00",
"merchant_debit_amount": "5.10",
"refund_fee": "0.10",
"refund_fee_allocation": "merchant",
"network_fee": null,
"configured_network_fee_amount": "0.00",
"network_fee_allocation": "platform",
"destination": "0x0000000000000000000000000000000000000001",
"destination_tag": null,
"status": "pending",
"environment": "live",
"tx_hash": null,
"failure_reason": null,
"description": "Partial refund for order 1001",
"metadata": null,
"completed_at": null,
"created_at": "2026-09-14T10:00:00Z",
"payment_id": null,
"refund_request_id": null
},
"meta": {
"request_id": "00000000-0000-0000-0000-000000000001"
}
}400 `validation_error`: The request format, parameters, headers, or field values are invalid.
Idempotent-Replayed"true"OptionalPresent and true when returning the stored outcome of an earlier execution.Example:
trueIdempotency-Key-Released"true"OptionalPresent and true after a proven no-effect refusal and confirmed release. This attempt bound nothing; a concurrent request may acquire the key.Example:
true
application/json
errorobjectRequiredThe error code and diagnostic message.- Unknown properties are rejected
Example:
{"code":"validation_error","message":"string","details":["string"]}code"validation_error"Required`validation_error`: The request format, parameters, headers, or field values are invalid.Example:
validation_errormessagestringRequiredHuman-readable description of the error.Example:
stringdetailsarray<string>OptionalOptional structured context (validation errors, etc.).Example:
["string"]
metaobjectRequired- Unknown properties are rejected
Example:
{"request_id":"00000000-0000-0000-0000-000000000001"}request_idstringRequiredCorrelation identifier issued by the API, also returned in the X-Request-ID response header. Client-supplied X-Request-ID values are replaced.Example:
string
{
"error": {
"code": "validation_error",
"message": "Request validation failed"
},
"meta": {
"request_id": "00000000-0000-0000-0000-000000000001"
}
}401 `unauthorized`: A valid API key is required.
Idempotent-Replayed"true"OptionalPresent and true when returning the stored outcome of an earlier execution.Example:
trueIdempotency-Key-Released"true"OptionalPresent and true after a proven no-effect refusal and confirmed release. This attempt bound nothing; a concurrent request may acquire the key.Example:
true
application/json
errorobjectRequiredThe error code and diagnostic message.- Unknown properties are rejected
Example:
{"code":"unauthorized","message":"string","details":["string"]}code"unauthorized"Required`unauthorized`: A valid API key is required.Example:
unauthorizedmessagestringRequiredHuman-readable description of the error.Example:
stringdetailsarray<string>OptionalOptional structured context (validation errors, etc.).Example:
["string"]
metaobjectRequired- Unknown properties are rejected
Example:
{"request_id":"00000000-0000-0000-0000-000000000001"}request_idstringRequiredCorrelation identifier issued by the API, also returned in the X-Request-ID response header. Client-supplied X-Request-ID values are replaced.Example:
string
{
"error": {
"code": "unauthorized",
"message": "Invalid API key"
},
"meta": {
"request_id": "00000000-0000-0000-0000-000000000001"
}
}403 `forbidden`: The API key does not have permission for this operation. `api_key_ip_forbidden`: The request source address is not allowed by the API key.
Idempotent-Replayed"true"OptionalPresent and true when returning the stored outcome of an earlier execution.Example:
trueIdempotency-Key-Released"true"OptionalPresent and true after a proven no-effect refusal and confirmed release. This attempt bound nothing; a concurrent request may acquire the key.Example:
true
application/json
errorobjectRequiredThe error code and diagnostic message.- Unknown properties are rejected
Example:
{"code":"forbidden","message":"string","details":["string"]}code"forbidden" | "api_key_ip_forbidden"Required`forbidden`: The API key does not have permission for this operation. `api_key_ip_forbidden`: The request source address is not allowed by the API key.Example:
forbiddenmessagestringRequiredHuman-readable description of the error.Example:
stringdetailsarray<string>OptionalOptional structured context (validation errors, etc.).Example:
["string"]
metaobjectRequired- Unknown properties are rejected
Example:
{"request_id":"00000000-0000-0000-0000-000000000001"}request_idstringRequiredCorrelation identifier issued by the API, also returned in the X-Request-ID response header. Client-supplied X-Request-ID values are replaced.Example:
string
{
"error": {
"code": "forbidden",
"message": "Permission denied"
},
"meta": {
"request_id": "00000000-0000-0000-0000-000000000001"
}
}404 `not_found`: The invoice was not found in the API key's merchant and environment scope.
Idempotent-Replayed"true"OptionalPresent and true when returning the stored outcome of an earlier execution.Example:
trueIdempotency-Key-Released"true"OptionalPresent and true after a proven no-effect refusal and confirmed release. This attempt bound nothing; a concurrent request may acquire the key.Example:
true
application/json
errorobjectRequiredThe error code and diagnostic message.- Unknown properties are rejected
Example:
{"code":"not_found","message":"string","details":["string"]}code"not_found"Required`not_found`: The invoice was not found in the API key's merchant and environment scope.Example:
not_foundmessagestringRequiredHuman-readable description of the error.Example:
stringdetailsarray<string>OptionalOptional structured context (validation errors, etc.).Example:
["string"]
metaobjectRequired- Unknown properties are rejected
Example:
{"request_id":"00000000-0000-0000-0000-000000000001"}request_idstringRequiredCorrelation identifier issued by the API, also returned in the X-Request-ID response header. Client-supplied X-Request-ID values are replaced.Example:
string
{
"error": {
"code": "not_found",
"message": "Resource not found"
},
"meta": {
"request_id": "00000000-0000-0000-0000-000000000001"
}
}409 `conflict`: The idempotency key is bound to a different request, or this request conflicts with an existing resource. `in_flight`: A request with this idempotency key is still executing. Retry the original request with the same key.
Idempotent-Replayed"true"OptionalPresent and true when returning the stored outcome of an earlier execution.Example:
trueIdempotency-Key-Released"true"OptionalPresent and true after a proven no-effect refusal and confirmed release. This attempt bound nothing; a concurrent request may acquire the key.Example:
true
application/json
errorobjectRequiredThe error code and diagnostic message.- Unknown properties are rejected
Example:
{"code":"conflict","message":"string","details":["string"]}code"conflict" | "in_flight"Required`conflict`: The idempotency key is bound to a different request, or this request conflicts with an existing resource. `in_flight`: A request with this idempotency key is still executing. Retry the original request with the same key.Example:
conflictmessagestringRequiredHuman-readable description of the error.Example:
stringdetailsarray<string>OptionalOptional structured context (validation errors, etc.).Example:
["string"]
metaobjectRequired- Unknown properties are rejected
Example:
{"request_id":"00000000-0000-0000-0000-000000000001"}request_idstringRequiredCorrelation identifier issued by the API, also returned in the X-Request-ID response header. Client-supplied X-Request-ID values are replaced.Example:
string
{
"error": {
"code": "conflict",
"message": "Request conflicts with the existing state"
},
"meta": {
"request_id": "00000000-0000-0000-0000-000000000001"
}
}422 `unprocessable_entity`: The invoice, refund amount, currency/network pair, destination, or balance does not allow this refund. `gate_unknown`: The requested currency and network pair is unknown.
Idempotent-Replayed"true"OptionalPresent and true when returning the stored outcome of an earlier execution.Example:
trueIdempotency-Key-Released"true"OptionalPresent and true after a proven no-effect refusal and confirmed release. This attempt bound nothing; a concurrent request may acquire the key.Example:
true
application/json
errorobjectRequiredThe error code and diagnostic message.- Unknown properties are rejected
Example:
{"code":"unprocessable_entity","message":"string","details":["string"]}code"unprocessable_entity" | "gate_unknown"Required`unprocessable_entity`: The invoice, refund amount, currency/network pair, destination, or balance does not allow this refund. `gate_unknown`: The requested currency and network pair is unknown.Example:
unprocessable_entitymessagestringRequiredHuman-readable description of the error.Example:
stringdetailsarray<string>OptionalOptional structured context (validation errors, etc.).Example:
["string"]
metaobjectRequired- Unknown properties are rejected
Example:
{"request_id":"00000000-0000-0000-0000-000000000001"}request_idstringRequiredCorrelation identifier issued by the API, also returned in the X-Request-ID response header. Client-supplied X-Request-ID values are replaced.Example:
string
{
"error": {
"code": "unprocessable_entity",
"message": "The request cannot be processed in the current state"
},
"meta": {
"request_id": "00000000-0000-0000-0000-000000000001"
}
}500 `internal_error`: The API could not complete the request. Contact support with the request ID if the error persists.
Idempotent-Replayed"true"OptionalPresent and true when returning the stored outcome of an earlier execution.Example:
trueIdempotency-Key-Released"true"OptionalPresent and true after a proven no-effect refusal and confirmed release. This attempt bound nothing; a concurrent request may acquire the key.Example:
true
application/json
errorobjectRequiredThe error code and diagnostic message.- Unknown properties are rejected
Example:
{"code":"internal_error","message":"string","details":["string"]}code"internal_error"Required`internal_error`: The API could not complete the request. Contact support with the request ID if the error persists.Example:
internal_errormessagestringRequiredHuman-readable description of the error.Example:
stringdetailsarray<string>OptionalOptional structured context (validation errors, etc.).Example:
["string"]
metaobjectRequired- Unknown properties are rejected
Example:
{"request_id":"00000000-0000-0000-0000-000000000001"}request_idstringRequiredCorrelation identifier issued by the API, also returned in the X-Request-ID response header. Client-supplied X-Request-ID values are replaced.Example:
string
{
"error": {
"code": "internal_error",
"message": "Internal server error"
},
"meta": {
"request_id": "00000000-0000-0000-0000-000000000001"
}
}503 `service_unavailable`: The service is temporarily unavailable. If the outcome is uncertain, retry the original request with the same idempotency key.
Idempotency-Key-Released"true"OptionalPresent and true after a proven no-effect refusal and confirmed release. This attempt bound nothing; a concurrent request may acquire the key.Example:
trueIdempotent-Replayed"true"OptionalPresent and true when returning the stored outcome of an earlier execution.Example:
true
application/json
errorobjectRequiredThe error code and diagnostic message.- Unknown properties are rejected
Example:
{"code":"service_unavailable","message":"string","details":["string"]}code"service_unavailable"Required`service_unavailable`: The service is temporarily unavailable. If the outcome is uncertain, retry the original request with the same idempotency key.Example:
service_unavailablemessagestringRequiredHuman-readable description of the error.Example:
stringdetailsarray<string>OptionalOptional structured context (validation errors, etc.).Example:
["string"]
metaobjectRequired- Unknown properties are rejected
Example:
{"request_id":"00000000-0000-0000-0000-000000000001"}request_idstringRequiredCorrelation identifier issued by the API, also returned in the X-Request-ID response header. Client-supplied X-Request-ID values are replaced.Example:
string
{
"error": {
"code": "service_unavailable",
"message": "Service temporarily unavailable"
},
"meta": {
"request_id": "00000000-0000-0000-0000-000000000001"
}
}