API Endpoints

Complete reference for all TextWatermark API endpoints. All requests and responses use JSON. Base URL: https://textwatermarking.com. See the Authentication Guide for details on JWT access tokens and API tokens (see the Authentication Guide for the format).

Authentication

These endpoints manage user registration, email verification, login, token refresh, logout, and password reset. No authentication header is required for any of these endpoints.

Register

POST /api/auth/register

Creates a new user account. A verification email is sent automatically.

Request Body:

{
  "email": "[email protected]",
  "password": "YourStrongPassword123!"
}

Successful Response (201 Created):

{
  "message": "Registration successful. Please check your email to verify your account.",
  "userId": "uuid-here"
}

Example cURL:

curl -X POST https://textwatermarking.com/api/auth/register \
     -H "Content-Type: application/json" \
     -d '{"email": "[email protected]", "password": "YourStrongPassword123!"}'

Verify Email

GET /api/auth/verify-email

Verifies the user's email address using the token sent in the verification email.

Query Parameters:

  • token (string, required): The verification token from the email link.

Successful Response (200 OK):

{
  "message": "Email verified successfully."
}

Example cURL:

curl "https://textwatermarking.com/api/auth/verify-email?token=<VERIFICATION_TOKEN>"

Login

POST /api/auth/login

Authenticates a user and returns a short-lived JWT access token. A long-lived refresh token (30 days) is set as an httpOnly cookie named refreshToken and is never exposed in the JSON body.

Request Body:

{
  "email": "[email protected]",
  "password": "YourStrongPassword123!"
}

Successful Response (200 OK):

{
  "accessToken": "eyJhbGci...",
  "user": {
    "id": "uuid-here",
    "email": "[email protected]",
    "role": "user",
    "plan": "free"
  }
}
// Set-Cookie: refreshToken=<token>; HttpOnly; Path=/; Max-Age=2592000; SameSite=Strict

Example cURL:

curl -X POST https://textwatermarking.com/api/auth/login \
     -H "Content-Type: application/json" \
     -c cookies.txt \
     -d '{"email": "[email protected]", "password": "YourStrongPassword123!"}'

Refresh Token

POST /api/auth/refresh

Issues a new JWT access token using the httpOnly refreshToken cookie. No request body needed.

Successful Response (200 OK):

{
  "accessToken": "eyJhbGci..."
}

Example cURL:

curl -X POST https://textwatermarking.com/api/auth/refresh \
     -b cookies.txt

Logout

POST /api/auth/logout

Revokes the current refresh token. Requires a valid JWT access token in the Authorization header.

Headers: Authorization: Bearer <ACCESS_TOKEN>

Successful Response (200 OK):

{
  "message": "Logged out successfully."
}

Example cURL:

curl -X POST https://textwatermarking.com/api/auth/logout \
     -H "Authorization: Bearer <ACCESS_TOKEN>"

Request Password Reset

POST /api/auth/request-reset

Sends a password reset link to the given email address if an account exists. Always returns 200 to prevent email enumeration.

Request Body:

{
  "email": "[email protected]"
}

Successful Response (200 OK):

{
  "message": "If an account with that email exists, a reset link has been sent."
}

Example cURL:

curl -X POST https://textwatermarking.com/api/auth/request-reset \
     -H "Content-Type: application/json" \
     -d '{"email": "[email protected]"}'

Reset Password

POST /api/auth/reset-password

Sets a new password using the token from the reset email.

Request Body:

{
  "token": "<RESET_TOKEN>",
  "newPassword": "NewStrongPassword456!"
}

Successful Response (200 OK):

{
  "message": "Password reset successfully."
}

Example cURL:

curl -X POST https://textwatermarking.com/api/auth/reset-password \
     -H "Content-Type: application/json" \
     -d '{"token": "<RESET_TOKEN>", "newPassword": "NewStrongPassword456!"}'

Users

User profile and account management endpoints. All require Authorization: Bearer <ACCESS_TOKEN>.

Get Profile

GET /api/v2/users/me

Returns the authenticated user's full profile including their API tokens and credit balance.

Headers: Authorization: Bearer <ACCESS_TOKEN>

Successful Response (200 OK):

{
  "user": {
    "id": "uuid-here",
    "email": "[email protected]",
    "role": "user",
    "status": "active",
    "plan": "free",
    "username": "myusername",
    "firstName": "Jane",
    "lastName": "Doe",
    "createdAt": "2025-01-01T00:00:00.000Z"
  },
  "tokens": [
    {
      "id": "token-uuid",
      "name": "My Token",
      "token_prefix": "mcp_a3f8e2b1",
      "permissions": ["encode","decode"],
      "is_revoked": false,
      "expires_at": null,
      "last_used_at": "2026-03-30T10:00:00.000Z",
      "created_at": "2025-06-01T00:00:00.000Z"
    }
  ],
  "credits": {
    "balance": 450
  }
}

Example cURL:

curl https://textwatermarking.com/api/v2/users/me \
     -H "Authorization: Bearer <ACCESS_TOKEN>"

Update Profile

PUT /api/v2/users/me

Updates the authenticated user's profile fields. Only include the fields you want to change.

Headers: Authorization: Bearer <ACCESS_TOKEN>

Request Body: (all fields optional)

{
  "username": "newusername",
  "firstName": "Jane",
  "lastName": "Smith"
}

Successful Response (200 OK):

{
  "message": "Profile updated successfully.",
  "user": {
    "id": "uuid-here",
    "username": "newusername",
    "firstName": "Jane",
    "lastName": "Smith"
  }
}

Example cURL:

curl -X PUT https://textwatermarking.com/api/v2/users/me \
     -H "Authorization: Bearer <ACCESS_TOKEN>" \
     -H "Content-Type: application/json" \
     -d '{"username": "newusername", "firstName": "Jane"}'

Change Password

PUT /api/v2/users/password

Changes the authenticated user's password. Requires the current password for verification.

Headers: Authorization: Bearer <ACCESS_TOKEN>

Request Body:

{
  "currentPassword": "OldPassword123!",
  "newPassword": "NewPassword456!"
}

Successful Response (200 OK):

{
  "message": "Password changed successfully."
}

Example cURL:

curl -X PUT https://textwatermarking.com/api/v2/users/password \
     -H "Authorization: Bearer <ACCESS_TOKEN>" \
     -H "Content-Type: application/json" \
     -d '{"currentPassword": "OldPassword123!", "newPassword": "NewPassword456!"}'

Get Credits

GET /api/v2/users/credits

Returns the authenticated user's current credit balance, plan, and subscription renewal date.

Headers: Authorization: Bearer <ACCESS_TOKEN>

Successful Response (200 OK):

{
  "balance": 450,
  "plan": "starter",
  "subscription_status": "active",
  "renews_at": "2026-04-30T00:00:00.000Z"
}

Example cURL:

curl https://textwatermarking.com/api/v2/users/credits \
     -H "Authorization: Bearer <ACCESS_TOKEN>"

List API Tokens

GET /api/v2/users/tokens

Returns all API tokens belonging to the authenticated user. The raw token value is never returned here — it is only shown once upon creation.

Headers: Authorization: Bearer <ACCESS_TOKEN>

Successful Response (200 OK):

{
  "tokens": [
    {
      "id": "token-uuid",
      "name": "Production Key",
      "token_prefix": "mcp_a3f8e2b1",
      "permissions": ["encode", "decode"],
      "is_revoked": false,
      "expires_at": null,
      "last_used_at": "2026-03-30T10:00:00.000Z",
      "created_at": "2025-06-01T00:00:00.000Z"
    }
  ]
}

Example cURL:

curl https://textwatermarking.com/api/v2/users/tokens \
     -H "Authorization: Bearer <ACCESS_TOKEN>"

Delete API Token

DELETE /api/v2/users/tokens/:tokenId

Revokes and deletes the specified API token. The token immediately stops working for watermark operations.

Headers: Authorization: Bearer <ACCESS_TOKEN>

Path Parameters:

  • tokenId (string, required): The UUID of the API token to delete.

Successful Response (200 OK):

{
  "message": "Token deleted successfully."
}

Example cURL:

curl -X DELETE https://textwatermarking.com/api/v2/users/tokens/token-uuid \
     -H "Authorization: Bearer <ACCESS_TOKEN>"

Verify JWT Token

GET /api/v2/users/verify

Checks whether the provided JWT access token is valid. Useful for client-side session validation.

Headers: Authorization: Bearer <ACCESS_TOKEN>

Successful Response (200 OK):

{
  "valid": true,
  "userId": "uuid-here",
  "role": "user"
}

Example cURL:

curl https://textwatermarking.com/api/v2/users/verify \
     -H "Authorization: Bearer <ACCESS_TOKEN>"

API Tokens

Create API tokens used to authenticate watermark operations. Requires Authorization: Bearer <ACCESS_TOKEN>. The raw token value is shown once on creation and cannot be retrieved again.

Create API Token

POST /api/v2/tokens

Creates a new API token. Optionally link it to a signing channel for automatic server-side encryption of watermark secrets.

Headers: Authorization: Bearer <ACCESS_TOKEN>

Request Body:

{
  "name": "Production Key",
  "permissions": ["encode", "decode"],
  "signing_channel_id": "channel-uuid"  // optional
}

Successful Response (201 Created):

{
  "token": "a3f8e2b1c9d47f6a2b5c8d1e9f3a4b7c2e5d8f1a4c7b2e5d8f1a4c7b2e5d8f1",
  "id": "token-uuid",
  "name": "Production Key",
  "token_prefix": "mcp_a3f8e2b1"
}
// Store the token value now — it will NOT be shown again.

Example cURL:

curl -X POST https://textwatermarking.com/api/v2/tokens \
     -H "Authorization: Bearer <ACCESS_TOKEN>" \
     -H "Content-Type: application/json" \
     -d '{"name": "Production Key", "permissions": ["encode", "decode"]}'

Signing Channels

Signing channels hold a server-managed encryption key. When an API token is linked to a channel, the watermark secret is automatically encrypted on encode and decrypted on decode — you never need to send the key to the client. Plan limits: Free = 0, Starter = 3, Pro = 20. Requires Authorization: Bearer <ACCESS_TOKEN>.

List Channels

GET /api/v2/channels

Returns all signing channels for the authenticated user.

Headers: Authorization: Bearer <ACCESS_TOKEN>

Successful Response (200 OK):

{
  "channels": [
    {
      "id": "channel-uuid",
      "name": "my-channel",
      "is_active": true,
      "active_tokens": 2,
      "created_at": "2025-06-01T00:00:00.000Z"
    }
  ]
}

Example cURL:

curl https://textwatermarking.com/api/v2/channels \
     -H "Authorization: Bearer <ACCESS_TOKEN>"

Create Channel

POST /api/v2/channels

Creates a new signing channel. A unique encryption key is generated automatically and stored server-side.

Headers: Authorization: Bearer <ACCESS_TOKEN>

Request Body:

{
  "name": "my-channel"
}

Successful Response (201 Created):

{
  "channel": {
    "id": "channel-uuid",
    "name": "my-channel"
  }
}

Example cURL:

curl -X POST https://textwatermarking.com/api/v2/channels \
     -H "Authorization: Bearer <ACCESS_TOKEN>" \
     -H "Content-Type: application/json" \
     -d '{"name": "my-channel"}'

Delete Channel

DELETE /api/v2/channels/:channelId

Deletes a signing channel. Any API tokens linked to this channel will no longer perform keyed signing.

Headers: Authorization: Bearer <ACCESS_TOKEN>

Path Parameters:

  • channelId (string, required): The UUID of the channel to delete.

Successful Response (200 OK):

{
  "message": "Channel deleted successfully."
}

Example cURL:

curl -X DELETE https://textwatermarking.com/api/v2/channels/channel-uuid \
     -H "Authorization: Bearer <ACCESS_TOKEN>"

Rotate Channel Secret

POST /api/v2/channels/:channelId/rotate

Generates a new encryption key for the channel. All API tokens currently bound to this channel are revoked automatically — you must issue new tokens and re-bind them.

⚠️ Breaking change: Watermarks encoded with the old channel secret become permanently undecodable after rotation. Use this endpoint only when a secret is compromised or as part of a deliberate key-rotation policy.

Headers: Authorization: Bearer <ACCESS_TOKEN>

Path Parameters:

  • channelId (string, required): The UUID of the channel to rotate.

Successful Response (200 OK):

{
  "message": "Channel secret rotated. Old watermarks can no longer be decoded. Bound tokens have been revoked — issue new tokens and re-bind them to this channel.",
  "channel_id": "channel-uuid",
  "channel_name": "my-channel",
  "tokens_revoked": 2
}

Example cURL:

curl -X POST https://textwatermarking.com/api/v2/channels/channel-uuid/rotate \
     -H "Authorization: Bearer <ACCESS_TOKEN>"

Watermarking

Core watermark encoding and decoding endpoints. These require an API token (Authorization: Bearer ), not a JWT access token. Each successful call costs 1 credit. On success the response includes an X-Credits-Remaining header. When credits reach 0, the API returns 402 Payment Required with a checkout_url field.

Encode

POST /api/watermark/encode

Embeds a hidden watermark (secret) into text using Unicode zero-width characters. The encoded text looks identical to the original.

Headers: Authorization: Bearer

Request Body:

{
  "text": "The quick brown fox jumps over the lazy dog.",
  "secret": "my-secret-watermark",
  "method": "auto",        // optional: "auto" (default) | "single" | "chunked" | "char"
  "chunkSize": 2,          // optional, required when method="chunked"
  "output": "full",        // optional: "full" (default) | "tokens"
  "firstLetterOnly": false // optional, default false
}

Method values: auto — smart routing based on text length (recommended); single — 1 byte per character; chunked — N bytes per character, requires chunkSize; char — single word or token only.

Successful Response (200 OK):

{
  "encoded": "The quick brown fox​‌ jumps over the lazy dog.",
  "keyed": false,
  "stats": {
    "originalLength": 44,
    "secretLength": 19,
    "encodedLength": 44,
    "chunkSize": 1,
    "method": "single"
  }
}
// Response header: X-Credits-Remaining: 449

Example cURL:

curl -X POST https://textwatermarking.com/api/watermark/encode \
     -H "Authorization: Bearer a3f8e2b1c9d47f6a..." \
     -H "Content-Type: application/json" \
     -d '{"text": "The quick brown fox jumps over the lazy dog.", "secret": "my-secret"}'

Decode

POST /api/watermark/decode

Extracts a hidden watermark from previously encoded text.

Headers: Authorization: Bearer

Request Body:

{
  "text": "The quick brown fox​‌ jumps over the lazy dog."
}

Successful Response (200 OK):

{
  "decoded": "my-secret-watermark",
  "keyed": false
}
// Response header: X-Credits-Remaining: 448

Example cURL:

curl -X POST https://textwatermarking.com/api/watermark/decode \
     -H "Authorization: Bearer a3f8e2b1c9d47f6a..." \
     -H "Content-Type: application/json" \
     -d '{"text": "The quick brown fox​‌ jumps over the lazy dog."}'

Encode (Robust)

POST /api/watermark/encode-robust

Encodes a watermark using the robust method, which is more resilient to minor text modifications such as whitespace normalization or copy-paste artifacts.

Headers: Authorization: Bearer

Request Body:

{
  "text": "The quick brown fox jumps over the lazy dog.",
  "secret": "my-secret-watermark"
}

Successful Response (200 OK):

{
  "encoded": "The quick brown fox​ jumps over the lazy dog.",
  "keyed": false,
  "stats": {
    "originalLength": 44,
    "secretLength": 19,
    "encodedLength": 44,
    "method": "robust"
  }
}
// Response header: X-Credits-Remaining: 447

Example cURL:

curl -X POST https://textwatermarking.com/api/watermark/encode-robust \
     -H "Authorization: Bearer a3f8e2b1c9d47f6a..." \
     -H "Content-Type: application/json" \
     -d '{"text": "The quick brown fox jumps over the lazy dog.", "secret": "my-secret"}'

Decode (Robust)

POST /api/watermark/decode-robust

Extracts a watermark embedded with the robust encoding method.

Headers: Authorization: Bearer

Request Body:

{
  "text": "The quick brown fox​ jumps over the lazy dog."
}

Successful Response (200 OK):

{
  "decoded": "my-secret-watermark",
  "keyed": false
}
// Response header: X-Credits-Remaining: 446

Example cURL:

curl -X POST https://textwatermarking.com/api/watermark/decode-robust \
     -H "Authorization: Bearer a3f8e2b1c9d47f6a..." \
     -H "Content-Type: application/json" \
     -d '{"text": "The quick brown fox​ jumps over the lazy dog."}'

Keyed Mode (Signing Channels)

When your API token is linked to a signing channel, the server automatically encrypts the secret on encode and decrypts it on decode using the channel's key. In this mode, keyed: true is returned in the response and you do not need to manage the secret on the client side.

Out of Credits (402)

When your credit balance reaches 0, all watermark endpoints return:

{
  "error": "Insufficient credits.",
  "checkout_url": "https://textwatermarking.com/billing/..."
}

Credits

Every watermark operation (encode or decode) costs 1 credit. Credits are replenished monthly based on your plan. The GET /api/v2/users/credits endpoint (documented in the Users section above) returns your current balance, plan, and renewal date.

Plan Monthly Credits Channel Limit
Free Limited 0
Starter 500 3
Pro 5000 20

Payments

Subscription management powered by LemonSqueezy. Requires Authorization: Bearer <ACCESS_TOKEN>.

Checkout

POST /api/payments/checkout

Creates a LemonSqueezy checkout session for the specified plan variant. Redirect the user to the returned checkout_url to complete payment.

Headers: Authorization: Bearer <ACCESS_TOKEN>

Request Body:

{
  "variant_id": 123456
}

Successful Response (200 OK):

{
  "checkout_url": "https://textwatermarking.lemonsqueezy.com/checkout/..."
}

Example cURL:

curl -X POST https://textwatermarking.com/api/payments/checkout \
     -H "Authorization: Bearer <ACCESS_TOKEN>" \
     -H "Content-Type: application/json" \
     -d '{"variant_id": 123456}'

Cancel Subscription

DELETE /api/payments/subscription

Cancels the authenticated user's active subscription at the end of the current billing period.

Headers: Authorization: Bearer <ACCESS_TOKEN>

Successful Response (200 OK):

{
  "cancelled": true,
  "message": "Your subscription will be cancelled at the end of the billing period."
}

Example cURL:

curl -X DELETE https://textwatermarking.com/api/payments/subscription \
     -H "Authorization: Bearer <ACCESS_TOKEN>"