Work with this as data
Every API here is available over the APIs.io API and to AI agents over MCP.
MCP server
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
Tools for apis
7 MCP tools reach this
find_apisBrowse and filter every API in the catalog.get_api_artifactsOne API's artifacts, grouped by type.get_openapiThe primary OpenAPI for this API.find_similar_apisAPIs that look like this one.apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.resolveTurn a domain, URL or GitHub org into the provider it belongs to.find_cohortsEvery scored population of providers in the catalog.
Call it yourself
curl for this page
This API
curl "https://apis.io/api/v1/apis/ahasend-routes-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"
Discovery needs no key. Ratings and market analysis are Pro.
Get an API key
Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.
A second provider on the same verified email joins the account you already have.
OpenAPI Specification
openapi: 3.2.0
info:
title: AhaSend API v2 Routes API
description: The AhaSend API v2 allows you to send transactional emails, manage domains, webhooks, routes, API keys, and view statistics.
version: 2.0.0
contact:
email: support@ahasend.com
license:
name: MIT
identifier: MIT
servers:
- url: https://api.ahasend.com
description: Production server
security:
- BearerAuth: []
tags:
- name: Routes
description: Manage inbound email routing
paths:
/v2/accounts/{account_id}/routes:
get:
summary: Get Routes
description: 'Returns a list of routes for the account. A domain-scoped key must
provide the matching `domain` query parameter; the global read role can
list without that filter.'
operationId: getRoutes
tags:
- Routes
parameters:
- name: account_id
in: path
required: true
description: Account ID
schema:
type: string
format: uuid
- name: domain
in: query
description: Domain to filter routes by. Required when using a domain-scoped routes:read:{domain} API key.
schema:
type: string
- name: limit
in: query
description: Maximum number of items to return (1-100)
schema:
type: integer
minimum: 1
maximum: 100
default: 100
- name: after
in: query
description: Pagination cursor for the next page. Provide the value provided in `next_cursor` from the response.
schema:
type: string
- name: before
in: query
description: Pagination cursor for the previous page.
schema:
type: string
security:
- BearerAuth:
- routes:read:all
- BearerAuth:
- routes:read:{domain}
x-code-samples:
- lang: go
label: AhaSend Go SDK
source: "package main\n\nimport (\n \"context\"\n \"fmt\"\n \"log\"\n\n \"github.com/AhaSend/ahasend-go/api\"\n \"github.com/google/uuid\"\n)\n\nfunc main() {\n // Create API client with authentication\n client := api.NewAPIClient(\n api.WithAPIKey(\"aha-sk-your-64-character-key\"),\n )\n\n accountID := uuid.New()\n\n // Create context for the API call\n ctx := context.Background()\n\n // Call the ping endpoint\n response, httpResp, err := client.RoutesAPI.GetRoutes(\n ctx,\n accountID,\n nil,\n nil,\n )\n if err != nil {\n log.Fatalf(\"Error getting routes: %v\", err)\n }\n\n // Check response\n if httpResp.StatusCode == 200 {\n fmt.Printf(\"✅ Status: %d\\n\", httpResp.StatusCode)\n if response != nil {\n fmt.Printf(\"Found %d routes\\n\", len(response.Data))\n }\n } else {\n fmt.Printf(\"❌ Unexpected status code: %d\\n\", httpResp.StatusCode)\n }\n}\n"
- lang: javascript
label: Node.js 22+ (AhaSend SDK)
source: 'import { AhaSendClient } from "@ahasend/sdk";
const client = AhaSendClient.fromEnv();
const page = await client.routes.list({ domain: "example.com", limit: 20 });
console.log("Routes listed.", { count: page.data.length });
'
responses:
'200':
description: List of routes
content:
application/json:
schema:
$ref: '#/components/schemas/PaginatedRoutesResponse'
'400':
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Forbidden
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
post:
summary: Create Route
description: 'Creates a new route for inbound email routing. Authorization uses the
domain in `recipient`: the global write role or its matching
domain-specific write role is sufficient.'
operationId: createRoute
tags:
- Routes
parameters:
- name: account_id
in: path
required: true
description: Account ID
schema:
type: string
format: uuid
- $ref: '#/components/parameters/IdempotencyKey'
security:
- BearerAuth:
- routes:write:all
- BearerAuth:
- routes:write:{domain}
x-code-samples:
- lang: go
label: AhaSend Go SDK
source: "package main\n\nimport (\n \"context\"\n \"fmt\"\n \"log\"\n\n \"github.com/AhaSend/ahasend-go/api\"\n \"github.com/AhaSend/ahasend-go/models/requests\"\n \"github.com/google/uuid\"\n)\n\nfunc main() {\n // Create API client with authentication\n client := api.NewAPIClient(\n api.WithAPIKey(\"aha-sk-your-64-character-key\"),\n )\n\n accountID := uuid.New()\n\n // Create context for the API call\n ctx := context.Background()\n\n // Call the ping endpoint\n response, httpResp, err := client.RoutesAPI.CreateRoute(\n ctx,\n accountID,\n requests.CreateRouteRequest{\n Name: \"My Route\",\n URL: \"https://mystartup.com/tickets\",\n Recipient: \"ticket-*@mystartup.com\",\n Enabled: true,\n Attachments: true,\n Headers: true,\n StripReplies: true,\n },\n )\n if err != nil {\n log.Fatalf(\"Error creating route: %v\", err)\n }\n\n // Check response\n if httpResp.StatusCode == 201 {\n fmt.Printf(\"✅ Status: %d\\n\", httpResp.StatusCode)\n if response != nil {\n fmt.Printf(\"Created route: %#v\\n\", response)\n }\n } else {\n fmt.Printf(\"❌ Unexpected status code: %d\\n\", httpResp.StatusCode)\n }\n}\n"
- lang: javascript
label: Node.js 22+ (AhaSend SDK)
source: "import { AhaSendClient } from \"@ahasend/sdk\";\n\nconst client = AhaSendClient.fromEnv();\nconst route = await client.routes.create(\n {\n name: \"Inbound messages\",\n url: \"https://example.com/inbound\",\n recipient: \"inbound@example.com\",\n },\n { idempotencyKey: \"sdk-sample-create-route\" },\n);\nconsole.log(\"Route created.\", { id: route.id, name: route.name });\n"
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateRouteRequest'
responses:
'201':
description: Route created successfully
headers:
Idempotent-Replayed:
$ref: '#/components/headers/IdempotentReplayed'
content:
application/json:
schema:
$ref: '#/components/schemas/CreatedRoute'
'400':
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Forbidden
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'409':
$ref: '#/components/responses/IdempotencyConflict'
'422':
$ref: '#/components/responses/IdempotencyPayloadMismatch'
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/v2/accounts/{account_id}/routes/{route_id}:
get:
summary: Get Route
description: 'Returns a specific route by ID. Access requires the global read role or
a read role matching the route''s recipient domain.'
operationId: getRoute
tags:
- Routes
parameters:
- name: account_id
in: path
required: true
description: Account ID
schema:
type: string
format: uuid
- name: route_id
in: path
required: true
description: Route ID
schema:
type: string
format: uuid
security:
- BearerAuth:
- routes:read:all
- BearerAuth:
- routes:read:{domain}
x-code-samples:
- lang: go
label: AhaSend Go SDK
source: "package main\n\nimport (\n \"context\"\n \"fmt\"\n \"log\"\n\n \"github.com/AhaSend/ahasend-go/api\"\n \"github.com/google/uuid\"\n)\n\nfunc main() {\n // Create API client with authentication\n client := api.NewAPIClient(\n api.WithAPIKey(\"aha-sk-your-64-character-key\"),\n )\n\n accountID := uuid.New()\n\n // Create context for the API call\n ctx := context.Background()\n\n // Call the ping endpoint\n response, httpResp, err := client.RoutesAPI.GetRoute(\n ctx,\n accountID,\n uuid.MustParse(\"c5a32c40-b351-439f-8230-779daed3e42c\"),\n )\n if err != nil {\n log.Fatalf(\"Error getting route: %v\", err)\n }\n\n // Check response\n if httpResp.StatusCode == 200 {\n fmt.Printf(\"✅ Status: %d\\n\", httpResp.StatusCode)\n if response != nil {\n fmt.Printf(\"Route: %#v\\n\", response)\n }\n } else {\n fmt.Printf(\"❌ Unexpected status code: %d\\n\", httpResp.StatusCode)\n }\n}\n"
- lang: javascript
label: Node.js 22+ (AhaSend SDK)
source: 'import { AhaSendClient } from "@ahasend/sdk";
const client = AhaSendClient.fromEnv();
const routeId = "00000000-0000-4000-8000-000000000006";
const route = await client.routes.get(routeId);
console.log("Route found.", { id: route.id, name: route.name });
'
responses:
'200':
description: Route details
content:
application/json:
schema:
$ref: '#/components/schemas/Route'
'400':
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Forbidden
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: Route not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
put:
summary: Update Route
description: 'Updates an existing route. A domain-scoped key must authorize the
existing recipient domain and, when `recipient` changes, the new domain;
`routes:write:all` satisfies both checks.'
operationId: updateRoute
tags:
- Routes
parameters:
- name: account_id
in: path
required: true
description: Account ID
schema:
type: string
format: uuid
- name: route_id
in: path
required: true
description: Route ID
schema:
type: string
format: uuid
security:
- BearerAuth:
- routes:write:all
- BearerAuth:
- routes:write:{domain}
x-code-samples:
- lang: go
label: AhaSend Go SDK
source: "package main\n\nimport (\n \"context\"\n \"fmt\"\n \"log\"\n\n \"github.com/AhaSend/ahasend-go\"\n \"github.com/AhaSend/ahasend-go/api\"\n \"github.com/AhaSend/ahasend-go/models/requests\"\n \"github.com/google/uuid\"\n)\n\nfunc main() {\n // Create API client with authentication\n client := api.NewAPIClient(\n api.WithAPIKey(\"aha-sk-your-64-character-key\"),\n )\n\n accountID := uuid.New()\n\n // Create context for the API call\n ctx := context.Background()\n\n // Call the ping endpoint\n response, httpResp, err := client.RoutesAPI.UpdateRoute(\n ctx,\n accountID,\n uuid.MustParse(\"c5a32c40-b351-439f-8230-779daed3e42c\"),\n requests.UpdateRouteRequest{\n Name: ahasend.String(\"Updated Name\"),\n URL: ahasend.String(\"https://mystartup.com/new-tickets\"),\n },\n )\n if err != nil {\n log.Fatalf(\"Error updating route: %v\", err)\n }\n\n // Check response\n if httpResp.StatusCode == 200 {\n fmt.Printf(\"✅ Status: %d\\n\", httpResp.StatusCode)\n if response != nil {\n fmt.Printf(\"Updated route: %#v\\n\", response)\n }\n } else {\n fmt.Printf(\"❌ Unexpected status code: %d\\n\", httpResp.StatusCode)\n }\n}\n"
- lang: javascript
label: Node.js 22+ (AhaSend SDK)
source: "import { AhaSendClient } from \"@ahasend/sdk\";\n\nconst client = AhaSendClient.fromEnv();\nconst routeId = \"00000000-0000-4000-8000-000000000006\";\nconst route = await client.routes.update(routeId, {\n url: \"https://example.com/inbound-v2\",\n});\nconsole.log(\"Route updated.\", { id: route.id, name: route.name });\n"
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateRouteRequest'
responses:
'200':
description: Route updated successfully
content:
application/json:
schema:
$ref: '#/components/schemas/Route'
'400':
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Forbidden
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: Route not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
delete:
summary: Delete Route
description: 'Deletes a route. Access requires the global delete role or a delete role
matching the route''s recipient domain.'
operationId: deleteRoute
tags:
- Routes
parameters:
- name: account_id
in: path
required: true
description: Account ID
schema:
type: string
format: uuid
- name: route_id
in: path
required: true
description: Route ID
schema:
type: string
format: uuid
security:
- BearerAuth:
- routes:delete:all
- BearerAuth:
- routes:delete:{domain}
x-code-samples:
- lang: go
label: AhaSend Go SDK
source: "package main\n\nimport (\n \"context\"\n \"fmt\"\n \"log\"\n\n \"github.com/AhaSend/ahasend-go/api\"\n \"github.com/google/uuid\"\n)\n\nfunc main() {\n // Create API client with authentication\n client := api.NewAPIClient(\n api.WithAPIKey(\"aha-sk-your-64-character-key\"),\n )\n\n accountID := uuid.New()\n\n // Create context for the API call\n ctx := context.Background()\n\n // Call the ping endpoint\n response, httpResp, err := client.RoutesAPI.DeleteRoute(\n ctx,\n accountID,\n uuid.MustParse(\"c5a32c40-b351-439f-8230-779daed3e42c\"),\n )\n if err != nil {\n log.Fatalf(\"Error deleting route: %v\", err)\n }\n\n // Check response\n if httpResp.StatusCode == 200 {\n fmt.Printf(\"✅ Status: %d\\n\", httpResp.StatusCode)\n if response != nil {\n fmt.Printf(\"Deleted route: %#v\\n\", response)\n }\n } else {\n fmt.Printf(\"❌ Unexpected status code: %d\\n\", httpResp.StatusCode)\n }\n}\n"
- lang: javascript
label: Node.js 22+ (AhaSend SDK)
source: 'import { AhaSendClient } from "@ahasend/sdk";
const client = AhaSendClient.fromEnv();
const routeId = "00000000-0000-4000-8000-000000000006";
const result = await client.routes.delete(routeId);
console.log("Route deleted.", { message: result.message });
'
responses:
'200':
description: Route deleted successfully
content:
application/json:
schema:
$ref: '#/components/schemas/SuccessResponse'
'400':
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Forbidden
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: Route not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
components:
headers:
IdempotencyInProgress:
description: 'Identifies an idempotency-key execution that is still in progress. This
header is emitted only on the specialized HTTP 409 response.
'
schema:
type: string
enum:
- 'false'
IdempotencyRetryAfter:
description: 'Positive whole number of seconds after which the in-progress execution
lease may be retried with the same key and unchanged request.
'
schema:
type: integer
minimum: 1
IdempotentReplayed:
description: 'Indicates that this response is a stored replay of a previous identical request.
When this header is absent, the response is not a stored replay. Ordinary fresh
responses do not send `false`; that value is reserved for an in-progress 409.
'
schema:
type: string
enum:
- 'true'
responses:
IdempotencyPayloadMismatch:
description: 'Idempotency key was already used with a different HTTP method, resolved
request path, or request body. This response includes neither
`Idempotent-Replayed` nor `Retry-After`.
'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
message: idempotency key was already used with a different request payload
IdempotencyConflict:
description: 'Request in progress. This response always includes
`Idempotent-Replayed: false` and a positive integer `Retry-After`.
Clients must use that header tuple, not the human-readable message, to
distinguish this retryable idempotency state from other HTTP 409s.
'
headers:
Idempotent-Replayed:
$ref: '#/components/headers/IdempotencyInProgress'
Retry-After:
$ref: '#/components/headers/IdempotencyRetryAfter'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
message: A request with this idempotency key is already in progress
schemas:
ErrorResponse:
type: object
description: 'Human-readable API error. The server currently sends no stable machine
error code. In particular, clients must not parse `message` to
distinguish IP-allow-list, scope, ownership, plan, self-lockout,
suppression-duplicate, or other errors that share an HTTP status.
'
additionalProperties: false
required:
- message
properties:
message:
type: string
description: Error description
example:
message: Error message
CreateRouteRequest:
type: object
required:
- name
- url
- recipient
properties:
name:
type: string
maxLength: 255
description: Route name
url:
type: string
format: uri
description: Webhook URL for the route
recipient:
type: string
maxLength: 255
description: Recipient filter
attachments:
type: boolean
description: Whether to include attachments in webhooks
default: false
headers:
type: boolean
description: Whether to include headers in webhooks
default: false
group_by_message_id:
type: boolean
description: Whether to group by message ID
default: false
strip_replies:
type: boolean
description: Whether to strip reply content
default: false
enabled:
type: boolean
description: Whether the route is enabled
default: true
example:
name: Support Route
url: https://example.com/webhook
recipient: support@example.com
enabled: true
PaginatedRoutesResponse:
type: object
required:
- object
- data
- pagination
properties:
object:
type: string
enum:
- list
description: Object type identifier
data:
type: array
items:
$ref: '#/components/schemas/Route'
description: Array of routes
pagination:
$ref: '#/components/schemas/PaginationInfo'
CreatedRoute:
allOf:
- $ref: '#/components/schemas/Route'
- type: object
properties:
secret:
type: string
description: Route signing secret. Returned only when the route is created.
required:
- secret
SuccessResponse:
type: object
required:
- message
properties:
message:
type: string
description: Success message
example:
message: Operation completed successfully
Route:
type: object
properties:
object:
type: string
enum:
- route
description: Object type identifier
id:
type: string
format: uuid
description: Unique identifier for the route
created_at:
type: string
format: date-time
description: When the route was created
updated_at:
type: string
format: date-time
description: When the route was last updated
name:
type: string
description: Route name
url:
type: string
format: uri
description: Webhook URL for the route
recipient:
type: string
description: Recipient filter; an empty string means no recipient filter
attachments:
type: boolean
description: Whether to include attachments in route payload
headers:
type: boolean
description: Whether to include headers in route payload
group_by_message_id:
type: boolean
description: Whether to group by message ID
strip_replies:
type: boolean
description: Whether to strip reply content
enabled:
type: boolean
description: Whether the route is enabled
success_count:
type: integer
minimum: 0
description: Number of successful calls
error_count:
type: integer
minimum: 0
description: Number of unsuccessful calls
errors_since_last_success:
type: integer
minimum: 0
description: 'Number of consecutive failed calls. Reset to zero by the next
successful call.
'
last_request_at:
type:
- string
- 'null'
format: date-time
description: When the route was last called
required:
- object
- id
- created_at
- updated_at
- name
- url
- recipient
- attachments
- headers
- group_by_message_id
- strip_replies
- enabled
- success_count
- error_count
- errors_since_last_success
- last_request_at
PaginationInfo:
type: object
required:
- has_more
properties:
has_more:
type: boolean
description: Whether more items exist after the last item of this page
next_cursor:
type: string
description: Pass as `after` to fetch the next page
previous_cursor:
type: string
description: Pass as `before` to fetch the previous page; absent on a `before` page when the start of the list has been reached
example:
has_more: true
next_cursor: eyJpZCI6MTIzNH0=
UpdateRouteRequest:
type: object
properties:
name:
type:
- string
- 'null'
maxLength: 255
description: Route name
url:
type:
- string
- 'null'
format: uri
description: Webhook URL for the route
recipient:
type:
- string
- 'null'
maxLength: 255
description: Recipient filter
attachments:
type:
- boolean
- 'null'
description: Whether to include attachments in webhooks
headers:
type:
- boolean
- 'null'
description: Whether to include headers in webhooks
group_by_message_id:
type:
- boolean
- 'null'
description: Whether to group by message ID
strip_replies:
type:
- boolean
- 'null'
description: Whether to strip reply content
enabled:
type:
- boolean
- 'null'
description: Whether the route is enabled
example:
name: Updated Support Route
enabled: false
parameters:
IdempotencyKey:
name: Idempotency-Key
in: header
required: false
description: 'Optional idempotency key for safe request retries. Must be a unique string for each logical request.
An identical request with a completed stored outcome returns the original status and body. An in-progress
execution returns 409, a changed method/path/body returns 422, and a released 5xx execution may run again.
Keys for non-secret responses expire after 24 hours. API-key create responses include a one-time `secret_key`,
so successful encrypted replay responses for those operations expire after 5 minutes.
'
schema:
type: string
maxLength: 255
example: user-12345-create-domain-20240101
securitySchemes:
BearerAuth:
type: http
scheme: bearer
bearerFormat: aha-sk-64-CHARACTER-RANDOM-STRING
description: 'API key for authentication. Non-empty Security Requirement values are
AhaSend API-key roles. Roles listed within one requirement object are
jointly required; separate requirement objects are alternatives.
'