POST
/auth
Recover subscription access via wallet signature
Description
Step 2 of the recovery flow. Verify ownership of the wallet that paid for the subscription by submitting the signed nonce. On success, **revokes all prior tokens for the wallet** (every active agent on this subscription will need to re-auth) and issues a fresh one. The wallet doesn't need to be online or hold gas — signature verification is offline (EIP-191 / secp256k1 / keccak).
Important: Your wallet address is your permanent account identity for this service. If you lose access to the wallet that paid for the subscription, you cannot recover your phone number or your remaining subscription time. Do not use ephemeral or per-session wallets. Recovery via signature requires the original wallet's private key.
Authentication
None. Public endpoint, no auth required.
Code samples
Pick your runtime. Every sample is in the page source — switching tabs is for your eyes.
curl https://agentnumber.really.com/auth \
--request POST \
--header 'content-type: application/json' \
--data '{
"wallet": "0x0000000000000000000000000000000000000000",
"nonce": "n_abcdef0123456789abcdef0123456789",
"signature": "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00"
}'interface AuthResponse {
token: string;
number: string;
expires_at: string;
}
const response = await fetch('https://agentnumber.really.com/auth', {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
wallet: '0x0000000000000000000000000000000000000000',
nonce: 'n_abcdef0123456789abcdef0123456789',
signature: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00'
})
});
if (!response.ok) {
throw new Error(`recover subscription access via wallet signature failed: ${response.status}`);
}
const data = (await response.json()) as AuthResponse;
requests.post("https://agentnumber.really.com/auth",
headers={
"content-type": "application/json"
},
json={
"wallet": "0x0000000000000000000000000000000000000000",
"nonce": "n_abcdef0123456789abcdef0123456789",
"signature": "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00"
}
)package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
requestUrl := "https://agentnumber.really.com/auth"
payload := strings.NewReader(`{
"wallet": "0x0000000000000000000000000000000000000000",
"nonce": "n_abcdef0123456789abcdef0123456789",
"signature": "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00"
}`)
req, _ := http.NewRequest("POST", requestUrl, payload)
req.Header.Add("content-type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(res)
fmt.Println(string(body))
}fetch('https://agentnumber.really.com/auth', {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
wallet: '0x0000000000000000000000000000000000000000',
nonce: 'n_abcdef0123456789abcdef0123456789',
signature: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00'
})
})import { request } from 'undici'
const { statusCode, body } = await request('https://agentnumber.really.com/auth', {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
wallet: '0x0000000000000000000000000000000000000000',
nonce: 'n_abcdef0123456789abcdef0123456789',
signature: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00'
})
})require 'uri'
require 'net/http'
url = URI("https://agentnumber.really.com/auth")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["content-type"] = 'application/json'
request.body = <<~JSON
{
"wallet": "0x0000000000000000000000000000000000000000",
"nonce": "n_abcdef0123456789abcdef0123456789",
"signature": "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00"
}
JSON
response = http.request(request)
puts response.read_bodyimport Foundation
var request = URLRequest(url: URL(string: "https://agentnumber.really.com/auth")!)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "content-type")
let jsonBody = #"""
{
"wallet": "0x0000000000000000000000000000000000000000",
"nonce": "n_abcdef0123456789abcdef0123456789",
"signature": "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00"
}
"""#
request.httpBody = jsonBody.data(using: .utf8)
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse,
200..<300 ~= httpResponse.statusCode else {
throw URLError(.badServerResponse)
}
print(String(data: data, encoding: .utf8) ?? "")val client = OkHttpClient()
val mediaType = MediaType.parse("application/json")
val body = RequestBody.create(mediaType, "{\n \"wallet\": \"0x0000000000000000000000000000000000000000\",\n \"nonce\": \"n_abcdef0123456789abcdef0123456789\",\n \"signature\": \"0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00\"\n}")
val request = Request.Builder()
.url("https://agentnumber.really.com/auth")
.post(body)
.addHeader("content-type", "application/json")
.build()
val response = client.newCall(request).execute()POST /auth HTTP/1.1
Host: agentnumber.really.com
content-type: application/json
Content-Type: application/json
{
"wallet": "0x0000000000000000000000000000000000000000",
"nonce": "n_abcdef0123456789abcdef0123456789",
"signature": "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00"
}HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://agentnumber.really.com/auth"))
.header("content-type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("{\n \"wallet\": \"0x0000000000000000000000000000000000000000\",\n \"nonce\": \"n_abcdef0123456789abcdef0123456789\",\n \"signature\": \"0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00\"\n}"))
.build();
HttpResponse<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());#import <Foundation/Foundation.h>
NSDictionary *headers = @{ @"content-type": @"application/json" };
NSDictionary *parameters = @{ @"wallet": @"0x0000000000000000000000000000000000000000",
@"nonce": @"n_abcdef0123456789abcdef0123456789",
@"signature": @"0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00" };
NSData *postData = [NSJSONSerialization dataWithJSONObject:parameters options:0 error:nil];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://agentnumber.really.com/auth"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:10.0];
[request setHTTPMethod:@"POST"];
[request setAllHTTPHeaderFields:headers];
[request setHTTPBody:postData];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(@"%@", error);
} else {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
NSLog(@"%@", httpResponse);
}
}];
[dataTask resume];using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://agentnumber.really.com/auth");
request.Content = new StringContent(
"""
{
"wallet": "0x0000000000000000000000000000000000000000",
"nonce": "n_abcdef0123456789abcdef0123456789",
"signature": "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00"
}
""",
System.Text.Encoding.UTF8, "application/json");
using var response = await client.SendAsync(request);#include <curl/curl.h>
int main(void) {
curl_global_init(CURL_GLOBAL_DEFAULT);
CURL *curl = curl_easy_init();
if (!curl) {
curl_global_cleanup();
return 1;
}
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST");
curl_easy_setopt(curl, CURLOPT_URL, "https://agentnumber.really.com/auth");
struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "content-type: application/json");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "{\n \"wallet\": \"0x0000000000000000000000000000000000000000\",\n \"nonce\": \"n_abcdef0123456789abcdef0123456789\",\n \"signature\": \"0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00\"\n}");
CURLcode res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
curl_slist_free_all(headers);
curl_global_cleanup();
return (int)res;
}import 'package:http/http.dart' as http;
void main() async {
final headers = <String,String>{
'content-type': 'application/json',
};
final body = r'{
"wallet": "0x0000000000000000000000000000000000000000",
"nonce": "n_abcdef0123456789abcdef0123456789",
"signature": "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00"
}';
final response = await http.post(Uri.parse('https://agentnumber.really.com/auth'), headers: headers, body: body);
print(response.body);
}let client = reqwest::Client::new();
let request = client
.post("https://agentnumber.really.com/auth")
.header("content-type", "application/json")
.json(&serde_json::json!({
"wallet": "0x0000000000000000000000000000000000000000",
"nonce": "n_abcdef0123456789abcdef0123456789",
"signature": "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00"
}));
let response = request.send().await?;$ch = curl_init("https://agentnumber.really.com/auth");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['content-type: application/json']);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'wallet' => '0x0000000000000000000000000000000000000000',
'nonce' => 'n_abcdef0123456789abcdef0123456789',
'signature' => '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00'
]));
curl_exec($ch);
curl_close($ch);Request body
| Field | Type | Description |
|---|---|---|
wallet required |
string |
0x-prefixed Ethereum-style address. Example: 0x0000000000000000000000000000000000000000 |
nonce required |
string |
Nonce previously issued by `/auth/nonce`. Example: n_abcdef0123456789abcdef0123456789 |
signature required |
string |
0x-prefixed 65-byte EIP-191 personal_sign output of `Sign in to really.com SMS\nNonce: <nonce>`. Example: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00 |
Example body
{
"wallet": "0x0000000000000000000000000000000000000000",
"nonce": "n_abcdef0123456789abcdef0123456789",
"signature": "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00"
}
Responses
200 Recovery successful. Token rotated, fresh bearer issued.
{
"token": "an_123xyz123xyz123xyz123xyz123xyz",
"number": "+15555550123",
"expires_at": "2027-04-30T18:24:01.000Z"
}
400 Request body is malformed (missing wallet, nonce, or signature).
{
"error": "invalid_request",
"message": "missing required fields: wallet, nonce, signature"
}
401 Nonce was unknown / already consumed / expired, OR the signature did not recover the claimed wallet.
{
"error": "signature_mismatch",
"message": "signature did not recover the claimed wallet"
}
402 No active subscription found for this wallet. Subscribe first via `/subscribe`.
{
"error": "no_active_subscription",
"warning": "Your wallet address is your account identity. If you lose access to this wallet, you cannot recover your subscription. Do not use ephemeral wallets."
}