curl --request POST \
--url https://api.hopscotchlabs.ai/v1/messages \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"model": "anthropic/claude-sonnet-4-5",
"max_tokens": 64,
"messages": [
{
"role": "user",
"content": "Say hi in five words."
}
]
}
'import requests
url = "https://api.hopscotchlabs.ai/v1/messages"
payload = {
"model": "anthropic/claude-sonnet-4-5",
"max_tokens": 64,
"messages": [
{
"role": "user",
"content": "Say hi in five words."
}
]
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
model: 'anthropic/claude-sonnet-4-5',
max_tokens: 64,
messages: [{role: 'user', content: 'Say hi in five words.'}]
})
};
fetch('https://api.hopscotchlabs.ai/v1/messages', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.hopscotchlabs.ai/v1/messages",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'model' => 'anthropic/claude-sonnet-4-5',
'max_tokens' => 64,
'messages' => [
[
'role' => 'user',
'content' => 'Say hi in five words.'
]
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.hopscotchlabs.ai/v1/messages"
payload := strings.NewReader("{\n \"model\": \"anthropic/claude-sonnet-4-5\",\n \"max_tokens\": 64,\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": \"Say hi in five words.\"\n }\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.hopscotchlabs.ai/v1/messages")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"model\": \"anthropic/claude-sonnet-4-5\",\n \"max_tokens\": 64,\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": \"Say hi in five words.\"\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.hopscotchlabs.ai/v1/messages")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"model\": \"anthropic/claude-sonnet-4-5\",\n \"max_tokens\": 64,\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": \"Say hi in five words.\"\n }\n ]\n}"
response = http.request(request)
puts response.read_bodyCreate a message (Anthropic Messages API)
Send a request in the Anthropic Messages format. Set stream: true for Anthropic’s own server-sent event stream.
The credential is read from either Authorization: Bearer or Anthropic’s own x-api-key header, so an unmodified Anthropic SDK works whichever way it authenticates. If both headers are present they must carry the same key: two different keys are refused with 401 rather than one being chosen.
max_tokens is required by Anthropic and is passed through as you send it. It is not defaulted here, and a request that leaves it out is refused by the provider in the provider’s own words. It is also the figure the credit held for the request is sized from. An anthropic-version header is not forwarded.
This route differs from Anthropic’s own API in one way: a refusal comes back in the OpenAI error envelope that every other route here uses, so an Anthropic SDK reads the sentence from error.message. Only POST /v1/messages is served. POST /v1/messages/count_tokens is not, and answers 404 with the code endpoint_not_supported.
Credit is held when the request is admitted and settled against the provider’s own token counts when it finishes. You are charged the provider’s rate at face value. Nothing is added per request.
To read back the tokens and charge of one request afterwards, send the value of the x-hopscotch-request-id response header to GET /v1/usage/{id}.
curl --request POST \
--url https://api.hopscotchlabs.ai/v1/messages \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"model": "anthropic/claude-sonnet-4-5",
"max_tokens": 64,
"messages": [
{
"role": "user",
"content": "Say hi in five words."
}
]
}
'import requests
url = "https://api.hopscotchlabs.ai/v1/messages"
payload = {
"model": "anthropic/claude-sonnet-4-5",
"max_tokens": 64,
"messages": [
{
"role": "user",
"content": "Say hi in five words."
}
]
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
model: 'anthropic/claude-sonnet-4-5',
max_tokens: 64,
messages: [{role: 'user', content: 'Say hi in five words.'}]
})
};
fetch('https://api.hopscotchlabs.ai/v1/messages', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.hopscotchlabs.ai/v1/messages",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'model' => 'anthropic/claude-sonnet-4-5',
'max_tokens' => 64,
'messages' => [
[
'role' => 'user',
'content' => 'Say hi in five words.'
]
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.hopscotchlabs.ai/v1/messages"
payload := strings.NewReader("{\n \"model\": \"anthropic/claude-sonnet-4-5\",\n \"max_tokens\": 64,\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": \"Say hi in five words.\"\n }\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.hopscotchlabs.ai/v1/messages")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"model\": \"anthropic/claude-sonnet-4-5\",\n \"max_tokens\": 64,\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": \"Say hi in five words.\"\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.hopscotchlabs.ai/v1/messages")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"model\": \"anthropic/claude-sonnet-4-5\",\n \"max_tokens\": 64,\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": \"Say hi in five words.\"\n }\n ]\n}"
response = http.request(request)
puts response.read_bodyAuthorizations
A workspace API key in an Authorization: Bearer header. A missing header, a scheme other than Bearer, a key of the wrong shape, and a key we reject are all 401.
Headers
Your Hopscotch API key, as an alternative to Authorization: Bearer. If you send both, they must carry the same key.
Body
An Anthropic Messages request. Fields beyond the ones named here are forwarded to the provider unchanged, so Anthropic's own documentation describes them.
A model id in provider/model form, or profile/{identifier} for one of your workspace's routing profiles. A bare model id is refused with 400 and the code model_id_not_pinned. The catalog must list the model for messages. List what you can call with GET /v1/models.
"anthropic/claude-sonnet-4-5"
The conversation turns, in Anthropic message form.
The most output tokens you will accept. Required, and passed to the provider as sent. The provider owns the ceiling for the model you name.
x >= 1Return the reply as Anthropic's own event stream instead of one JSON body.
A system prompt, as Anthropic's API takes it: a string or an array of content blocks.
Tool definitions, in Anthropic's form.
How the model may use the tools, in Anthropic's form.
Response
The message, in Anthropic's format as the provider produced it: a content array, a stop_reason, and a usage object with input_tokens and output_tokens.
When stream is true the body is text/event-stream instead, passed through in Anthropic's own event format.