transaction_expires_at estimates when they run out, last_valid_block_height says it. The terms live five minutes. So have the terms signed first, and call this route when the wallet is ready for the transaction: its 150 blocks start here, not while the customer reads. Have the wallet sign it at once, then send it with the terms signature to Submit. Always sign the transaction of the last call.terms.message with the message the wallet signed. The same text is the same terms: submit with the signature you hold. Another text is new terms: have the wallet sign this message, since submit refuses the old signature with invalid_terms_signature.terms.expires_at. New terms come after that, after a submit that spent them (one answered transaction_expired may have), or when the plan changed on chain.Limit: 60 calls a minute per API key.Guide: Subscribe from your server.
The version of this contract the call is written against. Without it, the call is served as the first version, 2026-10-02. Any other value is refused with 400 unsupported_version.
One of2026-10-02
The subscription's id, as POST /v1/subscriptions answered it.
Example:cmg1x0abc0001
The subscription, pending, with a new transaction and the terms it names, as POST /v1/subscriptions answers them.
The version the answer is written in: the one asked for, or 2026-10-02 when the call named none, or one this API does not serve.
One of2026-10-02
The id Mesub gave this request, on every answer, errors included, and in request_id on an error body. Quote it to Mesub support to find the request in our logs. Written by Mesub on each request: one sent with the request is ignored.
The calls this route allows the API key in a minute, or the IP of a call without a valid key. Sent on every answer the rate limit counted.
Limits:at least 1
What is left of them in the current minute.
Limits:at least 0
Seconds until the count starts again.
Limits:at least 0
Keep it: every later call names it.
One ofpending
Base64, signed by nobody. The wallet signs it without sending it.
The block height after which the transaction can no longer land.
When last_valid_block_height is expected to pass: an estimate, about half a minute after the answer. Mesub reads it from how fast block heights went (skipped slots included), taking the faster of the last half hour and the last 30 seconds, and counts the blocks the chain's tip is ahead of what it read, so the estimate errs early, not late, while the chain keeps its pace. The height is the truth: rebuild the transaction when the chain refuses it with an expired blockhash, before this time as after it. Use this for a countdown, or to ask for a fresh transaction before it runs out.
What subscribing costs the wallet in SOL, in lamports, as strings.
A deposit the customer gets back when the subscription is closed.
Null when the customer already has an authority for that token.
Spent, not returned.
Plain text, one fact per line. The wallet signs it as is, before the transaction.
Five minutes after the terms were handed out. Submit before it.
Bearer <API key>. The project's API key, which starts with SUB_. Keep it on your server. Where to get it.
curl -X POST "https://api.mesub.io/v1/subscriptions/cmg1x0abc0001/transaction" \ -H "Authorization: Bearer $MESUB_API_KEY"
{ "subscription": { "id": "cmg1x0abc0001", "status": "pending" }, "transaction": "gAIBBg0Xq3ZkT9yLmR4vP7wJ8sN2aHcVbE5uYtKfDgQxWz1oMiCj6rA0lSnUhBdXe2GqFpYv7mNcTz4RwK8sLjH5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQIDBAUGBwgJCgsM", "last_valid_block_height": "301245678", "transaction_expires_at": "2026-01-01T00:00:33.000Z", "costs": { "rent": { "subscription": "1969680", "authority": "1628640", "total": "3598320" }, "fee": { "signatures": 2, "per_signature": "5000", "priority": "0", "total": "10000" }, "total": "3608320" }, "terms": { "message": "Mesub: the terms of the subscription...\nAmount: 9.99 USDC every month\n...", "expires_at": "2026-01-01T00:05:00.000Z" } }