Sign a transaction (hosted)
Hosted signing: sign an unsigned transaction with private keys sent in the body. Never broadcasts.
# Sign a transaction (hosted)
`POST https://api.rarebl.com/open/tx/sign`
Sends private keys to Rarebl, which signs an unsigned transaction in memory and forgets the keys. It never broadcasts.
**Rate class:** write (6 per minute, burst 3, 300 per day, per IP). **Available since:** Open API v2.
**Try It is disabled for this endpoint.** A documentation page should never collect private keys, so Try It is off here. Use the snippets from your own server or an SDK.
## Request
Send a JSON body (`Content-Type: application/json`). No API key is needed: `X-API-KEY` is reserved and ignored.
Example: `curl -X POST "https://api.rarebl.com/open/tx/sign" -H "Content-Type: application/json" -H "X-Rarebl-Accept-Risk: hosted-signing" -d '{"unsigned":{"chain":"dash","format":"utxo-v1","tx":"0200000001139f6185036ea872d9e52bd7424ba9165f31b6479556b021fe4b8f3f42e8087a0100000000ffffffff0240420f00000000001976a91454072845a9ca039a5b585f5fd994d88d38feb31c88ac189f3419000000001976a9147fb0a45f00c25ff1cb67753221326c7c420b8b2388ac00000000","inputs":[{"txid":"7a08e8423f8f4bfe21b0569547b6315f16a94b42d72be5d972a86e0385619f13","vout":1,"address":"XnL1FvTKqe6aWfB8NrvgbUMuWCEA3dNxfF","value":"4.23879226","valueSats":"423879226","scriptPubKey":"76a9147fb0a45f00c25ff1cb67753221326c7c420b8b2388ac","scriptType":"p2pkh","sequence":4294967295}],"outputs":[{"address":"XiM9Afap6UEb5LDkcWrg2YyA28hifkZZKM","value":"0.01","valueSats":"1000000","change":false},{"address":"XnL1FvTKqe6aWfB8NrvgbUMuWCEA3dNxfF","value":"4.22879","valueSats":"422879000","change":true}],"fee":"0.00000226","feeSats":"226","vsize":226,"feeRate":"1"},"keys":{"encrypted":"BASE64URL_ENVELOPE_FROM_SDK"}}'`
## Headers
| Header | Required | Description |
| --- | --- | --- |
| `X-Rarebl-Accept-Risk` | Yes | Required acknowledgement that you are sending private keys to a hosted signer. The only accepted value is `hosted-signing`; without it the API answers `API403` `risk_not_acknowledged`. Example: `hosted-signing`. |
## Parameters
| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `unsigned` | object | Yes | The `unsigned` object from tx/build. |
| `keys` | object | Yes | The private keys. `{"encrypted": "<envelope>"}`: an envelope v2 encrypted to the signer key from [tx/signer-key](https://docs.rarebl.com/api/tx/signer-key), bound to the chain, the operation (`sign` for tx/sign, `send` for tx/send) and the digests or recipients it names, valid for at most 300 seconds and single-use. Also accepted: `{"privateKeys": ["<WIF or 64-hex>"]}`, which carries no binding. See [Hosted signing](https://docs.rarebl.com/guides/hosted-signing). |
## Responses
### API200 (HTTP 200): OK
The signed transaction, not broadcast.
```json
{
"status": 1,
"code": "API200",
"message": "success",
"data": {
"signed": {
"chain": "dash",
"hex": "0200000001139f6185036ea872d9e52bd7424ba9165f31b6479556b021fe4b8f3f42e8087a010000006a473044022047ac8e878352d3ebbde1c94ce3a10d057c24175747116f8288e5d794d12d482f0220217f36a485cae903c713331d877c1f64677e3622ad4010726870540656fe9dcb012103ad1d8e89212f0b92c74d23bb710c00662ad1470198ac48c43f7d6f93a2a26873ffffffff0240420f00000000001976a91454072845a9ca039a5b585f5fd994d88d38feb31c88ac189f3419000000001976a9147fb0a45f00c25ff1cb67753221326c7c420b8b2388ac00000000",
"txid": "8235cfafa0ef70d4a5135331c6889a7c15780b7aeb3bc20721571c2a4b51ffa7"
}
},
"go": null,
"meta": {
"requestid": "0123456789abcdef",
"notices": []
}
}
```
### API403 (HTTP 403): Forbidden
The header `X-Rarebl-Accept-Risk: hosted-signing` is missing.
```json
{
"status": 0,
"code": "API403",
"message": "Hosted signing needs the risk acknowledgement header.",
"data": {
"reason": "risk_not_acknowledged"
},
"go": null,
"meta": {
"requestid": "0123456789abcdef",
"notices": []
}
}
```
### API403 (HTTP 403): Forbidden
Every key must derive the address of an input it signs. Unused keys are refused.
```json
{
"status": 0,
"code": "API403",
"message": "A key does not match the input addresses.",
"data": {
"reason": "key_mismatch"
},
"go": null,
"meta": {
"requestid": "0123456789abcdef",
"notices": []
}
}
```
### API403 (HTTP 403): Forbidden
The envelope (`op: "sign"`) must list exactly the `toSign` digests that the signer recomputes from `unsigned`; an extra, missing or different digest is refused and nothing is signed.
```json
{
"status": 0,
"code": "API403",
"message": "The digests are not the ones the envelope names.",
"data": {
"reason": "digest_not_bound"
},
"go": null,
"meta": {
"requestid": "0123456789abcdef",
"notices": []
}
}
```
### API400 (HTTP 400): Bad Request
The envelope is unreadable, is a v1 envelope, names an unknown `kid`, has an `op` that does not match the route, or has an `exp` more than 300 seconds ahead. Fetch [tx/signer-key](https://docs.rarebl.com/api/tx/signer-key) again and encrypt again with SDK 0.2.0 or later.
```json
{
"status": 0,
"code": "API400",
"message": "The envelope could not be read.",
"data": {
"reason": "bad_envelope"
},
"go": null,
"meta": {
"requestid": "0123456789abcdef",
"notices": []
}
}
```
### API400 (HTTP 400): Bad Request
The envelope's `exp` has passed. Make a new envelope right before the request.
```json
{
"status": 0,
"code": "API400",
"message": "The envelope has expired.",
"data": {
"reason": "envelope_expired"
},
"go": null,
"meta": {
"requestid": "0123456789abcdef",
"notices": []
}
}
```
### API409 (HTTP 409): Conflict
An envelope opens once. Make a new envelope for every request, including a retry.
```json
{
"status": 0,
"code": "API409",
"message": "The envelope was already used.",
"data": {
"reason": "envelope_replayed"
},
"go": null,
"meta": {
"requestid": "0123456789abcdef",
"notices": []
}
}
```
### API400 (HTTP 400): Bad Request
The `chain` in the envelope is not the chain of the request.
```json
{
"status": 0,
"code": "API400",
"message": "The envelope is for another chain.",
"data": {
"reason": "chain_mismatch"
},
"go": null,
"meta": {
"requestid": "0123456789abcdef",
"notices": []
}
}
```
### API400 (HTTP 400): Bad Request
A key-like value was found in the query string. The request is dropped unprocessed.
```json
{
"status": 0,
"code": "API400",
"message": "Keys must be sent in the request body.",
"data": {
"reason": "plaintext_key_in_query"
},
"go": null,
"meta": {
"requestid": "0123456789abcdef",
"notices": []
}
}
```
### API422 (HTTP 422): Unprocessable Entity
The signer could not produce a valid signature.
```json
{
"status": 0,
"code": "API422",
"message": "Signing failed.",
"data": {
"reason": "bad_signature"
},
"go": null,
"meta": {
"requestid": "0123456789abcdef",
"notices": []
}
}
```
### API429 (HTTP 429): Too Many Requests
Rate limited (write class: 6 per minute, burst 3, 300 per day, per IP). A `Retry-After` header says how long to wait, in seconds.
```json
{
"status": 0,
"code": "API429",
"message": "Too many requests. Please retry shortly.",
"data": null,
"go": null,
"meta": {
"requestid": "0123456789abcdef",
"notices": []
}
}
```
### API502 (HTTP 502): Bad Gateway
The node is unavailable, still syncing or timed out. Retry later with a short back-off.
```json
{
"status": 0,
"code": "API502",
"message": "The node is unavailable or timed out. Please try again.",
"data": null,
"go": null,
"meta": {
"requestid": "0123456789abcdef",
"notices": []
}
}
```
## Notes
- Walkthrough: [Hosted signing](https://docs.rarebl.com/guides/hosted-signing). To keep keys on your side, [sign locally](https://docs.rarebl.com/guides/sign-locally) instead.
- Keys go in a POST body only, and the header `X-Rarebl-Accept-Risk: hosted-signing` is mandatory. An envelope v2 with `op: "sign"` and the `digests` of the build is bound to what it names; a plain `privateKeys` body is accepted but carries no binding.
- Every key must derive the address of an input it signs, at most 10 inputs per request. Keys are used in memory by a separate signer process and wiped after use; request bodies on this path are excluded from all logging. Replies are `Cache-Control: no-store`.
- Write class: 6 per minute, burst 3, 300 per day per IP.