Skip to content
TurguzDocsTürkçeOpen the panel

Services

API pool

Open one of your project's APIs to your other projects, to other tenants or to the internet with a key, and call another project's API from your own app. Writing an app that works with the pool, with example code.

Every environment runs in its own network: one of your projects can't reach another project's site from the inside. The API havuzu (API pool) opens this through a controlled gate.

  • The project that offers its API to the pool is the provider; the project that calls that API is the consumer.
  • The provider's environment adds its API to the pool as an endpoint.
  • The consumer's environment connects to that endpoint and gets its own key to call it.
  • The provider decides who may connect. The platform checks the key; the provider's app doesn't need its own key check.

Example: your ses-tanima project offers a speech recognition API, and your haber-sitesi project wants to call it. The ses-tanima environment adds the endpoint to the pool, the haber-sitesi environment connects to it. No other environment can reach this endpoint.

#How it works

  1. The provider adds the endpoint to the pool on the API havuzu (API pool) tab of the environment that serves the API, and chooses who sees it.
  2. The consumer finds the endpoint on the API havuzu (API pool) page and connects its own environment with «Bağlan» (Connect). If the endpoint requires approval, the provider approves.
  3. Once the connection opens, the endpoint's address and key are written to the consumer's environment as variables. The consumer redeploys its environment.
  4. The consumer's app calls the endpoint at this address, with the key. The provider's app receives the request and recognises the caller from a request header.

What the apps need to do in these steps is in Writing a compatible provider and Writing a compatible consumer; examples you can copy and run are in Example: two small apps.

#Adding an endpoint to the pool

  1. Open the environment's API pool tab

    On the Projects page, select the environment that serves the API and switch to the API havuzu (API pool) tab. The Havuza verdiği uçlar (Endpoints it offers) section lists this environment's endpoints.

  2. «Ucu havuza ekle» (Add endpoint to the pool)

    Give it an Uç adı (Endpoint name): lowercase letters, digits and hyphens. In the pool, the endpoint appears with your tenant's name and this name. In Açıklama (Description), write what the endpoint does and how to call it; whoever connects will read it.

  3. Choose who sees it

    Choose the visibility in the Kim görür ve bağlanır (Who sees and connects) section (below), then «Havuza ekle» (Add to pool). To change the endpoint later, open the «Ucun ayarları» (Endpoint settings) window on its row.

People with write access to the project add and change endpoints. Opening an endpoint beyond your tenant, and creating or renewing external keys, is for those who can manage the project. Removing an endpoint in another team's project needs a manage share.

An endpoint belongs to an environment: when the environment is redeployed, the endpoint stays the same and its address doesn't change. In an environment with several apps, you choose which app the endpoint goes to and, if you like, a path prefix (such as /v1).

#Visibility

VisibilityWho sees it and can connect
«Kapalı» (Off)Nobody can connect; the endpoint isn't shown in the pool. Those who manage the project still see it on the environment's tab.
«Ekip içi» (Within team)Only projects in the project's team. Offered in tenants with team isolation turned on.
«Kiracı içi» (Within tenant)All projects in your tenant.
«Platform»All tenants on this platform.
«İnternet» (Internet)From the internet, with a key; you choose which addresses may reach it. See below: Opening an endpoint to the internet.

A connection from another tenant or (with team isolation on) from another team needs approval by default: you get a notification when the request arrives, and the connection waits until you approve it. Those who can manage the project can turn this approval off in the endpoint's settings.

If you narrow the visibility (for example to Kapalı (Off)), connections outside the new scope are closed immediately; the panel first tells you how many connections are affected and asks for confirmation. The owner of a closed connection gets a notification.

If the project moves to another tenant or team, the endpoint goes with it and its connections are checked again: a connection outside the scope in the new place is closed, and the other side gets a notification. When moving to another tenant, a connection that would leave the tenant without approval is closed too; you can request the connection again and get it approved.

When you turn team isolation on, the preview shows which connections will close and which will go on without approval. A connection that goes on without approval isn't cut; if you don't want it, close it from the endpoint's connections.

Someone who can't see the project sees the endpoint's name and description in the pool; in place of the project and environment names it says "Gizli proje" (Hidden project).

#Connecting to an endpoint

  1. Find the endpoint in the pool

    The API havuzu (API pool) page in the left menu lists the endpoints open to you: first your own tenant's, then those opened to the platform.

  2. «Bağlan» (Connect)

    Press «Bağlan» (Connect) on the endpoint's row and choose the project and environment that will call the API. You need a project whose environment you can write to.

  3. Redeploy

    Once the connection opens, two variables are added to your environment: the endpoint's address and its key. Your app sees them once the environment is redeployed; the panel reminds you.

If the endpoint requires approval, the connection waits until it's approved; you can withdraw the request with «İsteği geri çek» (Withdraw request). Once it's approved you get a notification; the variables are written then.

The connection's total usage counts are visible to the side offering the endpoint; request contents and addresses are not. Details: Tracking endpoint usage.

The variable names and how your app calls the endpoint are in Writing a compatible consumer. The key's value isn't shown in the panel; it's only passed to your app.

#Managing connections

The «Bağlantılar» (Connections) window on the endpoint's row shows who is connected to your endpoint. Only those who can approve the connection see the name of an environment connecting from another tenant; everyone else sees «Başka kiracı» (Another tenant) on that row.

  • «Onayla» (Approve) and «Reddet» (Reject): for pending requests.
  • «Bağlantıyı kapat» (Close connection): that environment's calls are rejected immediately and its variables are removed. The same connection can't be reopened; if needed, the environment connects again.
  • «Anahtarı yenile» (Renew key): the new key is valid immediately and is written to the connected environment's settings. You choose when the old key stops working: when the connected environment is redeployed (no interruption) or immediately (if the key has leaked).

The connecting side can also renew its own connection's key and close the connection.

Removing an endpoint from the pool closes all of its connections; it doesn't touch the environment or the app.

#Tracking endpoint usage

Usage has its own page. Open it from the link on the API pool page, or with the «Kullanım» (Usage) button in the environment's API pool tab. The endpoint, connection, key and range you pick stay in the page address: someone you share the address with opens the same view if they have access.

The page lists how many requests each key made: a masked key such as tgz_......xd31, the project, the request count, failed requests and last use. The full key is never stored or shown. A renewed key is counted on a separate row from the old one, so you can see whether the old key is still being called. Click a row to open that key's distribution over time. A key whose ending isn't on record appears with its ID; renewing it brings the mask.

The project that offers an endpoint sees who uses it and how much; this needs write access to the project. You choose the range, from the last hour to the last month.

  • Requests: how many requests arrived in the range.
  • Successful: the success rate of requests that reached your app. Your app's 4xx and 5xx responses are counted separately.
  • Response time: the typical time and the time of slow requests. For a streamed response it is the time of the whole response.
  • Rejected: calls that never reached your app, with the reason: key, address or path, limit.
  • Last request: shows you a connection that has not called for a while.

The same numbers appear per connection, and each row in the «Bağlantılar» (Connections) window shows a short summary. A request with an unrecognized key cannot be attributed to a connection and is counted on a separate line: if your app gets errors, check the consumer's key first.

The connecting side sees its own usage and keys too: in the environment's API pool tab, in the «Bağlandığı uçlar» (Connected endpoints) section, press «Kullanım» (Usage) on the connection. Anyone who can see the environment can look. Only that environment's connection is counted here; the endpoint's total and other connections are not shown.

If the panel says measurement stopped or that the numbers may be incomplete, requests in that period were not counted. Your app is not affected and earlier numbers stay in place. A project you can't see appears in the list as «Başka bir proje» (Another project).

Only total counts are kept. Request contents, the sender's address, headers and path parameters are not kept.

#Key lifetime

Keys don't expire by default. You can choose an Anahtar ömrü (Key lifetime) in the endpoint's settings; if you don't, the lifetime from the pool setting applies.

  • The tenant admin sets the tenant's default lifetime and ceiling on the API havuzu (API pool) page. No key lives longer than the ceiling.
  • The key's expiry is shown in the Bağlandığı uçlar (Endpoints it connects to) section and in the «Bağlantılar» (Connections) window.
  • When a key expires, its connection is closed. To avoid an interruption, renew it with «Anahtarı yenile» (Renew key) before it expires and redeploy the environment.

#Limits

You set the endpoint's limits in its settings: per key, requests per minute, a short burst and the number of requests running at the same time; for the endpoint, the size of the request body and the longest response time. The defaults are shown in the panel. On top of these, upper limits apply to every endpoint; they are shown in the endpoint's settings.

A caller over the request or same-time limit gets 429; the response says how long to wait. Long-running and streaming responses (AI models, audio) are passed through without being cut, as they arrive.

#Writing a compatible provider

The app offering the endpoint is an ordinary web app; the pool needs no extra library.

  • Listening: like every Turguz app, it listens on 0.0.0.0 and on the port in the PORT variable. The pool passes the request to the app the endpoint belongs to.
  • Path: whatever the consumer appends to the address is the path your app receives: appending merhaba gives /merhaba. If the endpoint has a path prefix, it goes in front: with the prefix /v1, /v1/merhaba.
  • Key: the platform checks the key; a request with an invalid key never reaches your app. The Authorization header isn't passed to your app. If your app expects its own key in that header, remove that check on the paths you open to the pool.
  • Recognising the caller: every request from the pool carries the X-Turguz-Tuketici header. Its value depends on the caller:
CallerX-Turguz-Tuketici value
In your tenant, an environment of a project open to the whole tenant<project name>/<environment name>/<connection>
In your tenant, an environment of a hidden project or (with team isolation on) a project in a team's areakiraci-ici/<connection>
An environment in another tenantkiraci-disi/<connection>
From the internet, with an external keydis/<connection>

<connection> is a short ID unique to the connection and is always the last part of the header: requests from the same connection always carry the same value, and it stays the same when the key is renewed; if the environment connects again, it gets a new one. Identify the caller by this last part: when the project's visibility or the team setting changes, the other parts change on their own and the last part doesn't. This needs no redeploy. If the project is named dis, kiraci-disi or kiraci-ici, the header takes the form kiraci-ici/<connection>. You aren't told the name of a project closed to you, or the tenant, project or environment of a caller from another tenant. Use the header to keep records per caller, to apply your own allowance or to tailor the response to the caller.

Requests also carry the X-Turguz-Anahtar header: it is the ID of the calling key, not the key itself. It changes when the key is renewed; the connection stays the same. You don't need this header unless you want to tell the old and new keys apart.

  • Request body: a request that states its body length and is over the endpoint's limit is rejected with 413 before it reaches your app. Don't act on a body before reading it to the end and checking that all of it arrived.
  • Streaming: the response is passed on without buffering. For a streaming response such as line-by-line JSON, server-sent events or audio, flush after each part; the consumer receives the parts as they come. For long streams, choose a long enough response time for the endpoint.
  • Errors: the status code and body your app returns go to the consumer unchanged; give a short explanation in a JSON body. If your app can't answer, the consumer gets 502, 503 or 504.
  • Heavy work: for heavy work such as an AI model or audio generation, limit the number of jobs running at the same time in your app as well and return 429 when it's full; choose the endpoint's Eşzamanlı istek (Concurrent requests) limit to match.

#Writing a compatible consumer

The consuming app is an ordinary web app too, and the pool needs no extra library. It calls the endpoint from its own server-side code, with the address and key written to its environment. The rules below are the same whichever language you write in; the example apps follow them as well.

  • Address and key: once the connection opens, two variables are written to your environment. Their names derive from the endpoint's name: the name is upper-cased and hyphens become underscores.
VariableContains
TURGUZ_API_<UÇ ADI>_URLThe endpoint's address. It ends with /.
TURGUZ_API_<UÇ ADI>_ANAHTARIThis connection's key.

For example, for an endpoint named ornek-api: TURGUZ_API_ORNEK_API_URL and TURGUZ_API_ORNEK_API_ANAHTARI. You can also copy the names on the environment's API havuzu (API pool) tab, in the Bağlandığı uçlar (Endpoints it connects to) section. Your app sees the variables once the environment is redeployed; if a variable is missing, the environment isn't connected to the endpoint or hasn't been redeployed since it connected.

  • Calling: append the path to the address without a leading /. Send the key on every request in the Authorization: Bearer <key> header; don't put it in the address or the query string. The request looks like this:
bash
curl -H "Authorization: Bearer $TURGUZ_API_ORNEK_API_ANAHTARI" "${TURGUZ_API_ORNEK_API_URL}merhaba"
  • Keep the key on the server: call the endpoint from your server-side code; don't send the key to the browser or a mobile app, and don't write it to logs. The address works only from the app in your connected environment; it can't be reached from a browser or from your own computer. To call from outside Turguz, the endpoint has to be opened to the internet.
  • Streaming: read a streaming response part by part and pass it on to your own client as it arrives. Choose your client's timeout to match the endpoint's response time.
  • Errors: act according to the Error codes table. On 429, wait and try again; on 502, 503 and 504, the app offering the endpoint isn't running, try again a little later; if 401 persists, check the connection's state on the environment's API havuzu (API pool) tab.

#Example: two small apps

The apps below need no extra library. The provider offers an endpoint added to the pool as ornek-api: JSON at the merhaba path and a line-by-line streaming response at the akis path. The consumer runs in the environment that connects to this endpoint; when opened, it calls both paths and shows the result. Each one is deployed in its own project and environment.

#Provider

#Node.js

javascript
// provider.js: an example app offering an endpoint to the pool. No extra library: node provider.js
const http = require("node:http");

function json(res, code, data) {
  res.writeHead(code, { "Content-Type": "application/json; charset=utf-8" });
  res.end(JSON.stringify(data));
}

http.createServer((req, res) => {
  // On a request from the pool, the caller's identity is in this header.
  const caller = req.headers["x-turguz-tuketici"] || "unknown";
  // Identify the connection by the last part: the other parts can change.
  const connection = caller.split("/").pop();
  const path = new URL(req.url, "http://app").pathname;

  if (req.method === "GET" && path === "/merhaba") {
    return json(res, 200, { message: "Hello", caller, connection });
  }
  if (req.method === "GET" && path === "/akis") {
    // Line-by-line stream: each line is a separate JSON and leaves as soon as it's written.
    res.writeHead(200, { "Content-Type": "application/x-ndjson" });
    res.flushHeaders(); // headers leave without waiting for the first line
    let count = 0;
    const timer = setInterval(() => {
      count += 1;
      res.write(JSON.stringify({ line: count }) + "\n");
      if (count === 5) {
        clearInterval(timer);
        res.end();
      }
    }, 1000);
    res.on("close", () => clearInterval(timer)); // stops if the consumer gives up
    return;
  }
  json(res, 404, { error: "not_found" });
}).listen(Number(process.env.PORT || 8080), "0.0.0.0");

#Python

python
# provider.py: an example app offering an endpoint to the pool. Standard library only: python3 provider.py
import json
import os
import time
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer


class App(BaseHTTPRequestHandler):
    def _json(self, code, data):
        body = json.dumps(data).encode()
        self.send_response(code)
        self.send_header("Content-Type", "application/json; charset=utf-8")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def do_GET(self):
        # On a request from the pool, the caller's identity is in this header.
        caller = self.headers.get("X-Turguz-Tuketici", "unknown")
        # Identify the connection by the last part: the other parts can change.
        connection = caller.rsplit("/", 1)[-1]

        if self.path == "/merhaba":
            return self._json(200, {"message": "Hello", "caller": caller, "connection": connection})
        if self.path == "/akis":
            # Line-by-line stream: each line is a separate JSON and leaves as soon as it's written.
            self.send_response(200)
            self.send_header("Content-Type", "application/x-ndjson")
            self.end_headers()
            for count in range(1, 6):
                self.wfile.write((json.dumps({"line": count}) + "\n").encode())
                self.wfile.flush()
                time.sleep(1)
            return
        self._json(404, {"error": "not_found"})


ThreadingHTTPServer(("0.0.0.0", int(os.environ.get("PORT", "8080"))), App).serve_forever()

#Consumer

#Node.js

javascript
// consumer.js: an example app in the environment connected to the ornek-api endpoint. Node 18 or later: node consumer.js
const http = require("node:http");

const ADDRESS = process.env.TURGUZ_API_ORNEK_API_URL || ""; // ends with "/"
const KEY = process.env.TURGUZ_API_ORNEK_API_ANAHTARI || "";

const EXPLANATION = {
  401: "the key is invalid or expired, or the connection was closed",
  413: "the request body is over the endpoint's limit",
  429: "the endpoint's request limit is full, try again a little later",
  502: "the app offering the endpoint isn't answering right now",
  503: "the app offering the endpoint isn't answering right now",
  504: "the app offering the endpoint didn't answer in time",
};

async function pool(path) {
  // The path is appended to the address without a leading "/"; the key goes in the header on every request.
  const answer = await fetch(ADDRESS + path, { headers: { Authorization: `Bearer ${KEY}` } });
  if (!answer.ok) throw new Error(`${answer.status}, ${EXPLANATION[answer.status] || (await answer.text())}`);
  return answer;
}

http.createServer(async (req, res) => {
  res.setHeader("Content-Type", "text/plain; charset=utf-8");
  if (!ADDRESS || !KEY) {
    res.statusCode = 503;
    return res.end("The environment isn't connected to the endpoint or wasn't redeployed after connecting.\n");
  }
  try {
    const data = await (await pool("merhaba")).json();
    const stream = await pool("akis");
    res.write(`${data.message}, caller: ${data.caller}\n`);
    for await (const part of stream.body) res.write(part); // the streaming response is passed on as it arrives
    res.end();
  } catch (error) {
    if (!res.headersSent) res.statusCode = 502;
    res.end(`API pool: ${error.message}\n`);
  }
}).listen(Number(process.env.PORT || 8080), "0.0.0.0");

#Python

python
# consumer.py: an example app in the environment connected to the ornek-api endpoint. Standard library only: python3 consumer.py
import json
import os
import urllib.error
import urllib.request
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer

ADDRESS = os.environ.get("TURGUZ_API_ORNEK_API_URL", "")  # ends with "/"
KEY = os.environ.get("TURGUZ_API_ORNEK_API_ANAHTARI", "")

EXPLANATION = {
    401: "the key is invalid or expired, or the connection was closed",
    413: "the request body is over the endpoint's limit",
    429: "the endpoint's request limit is full, try again a little later",
    502: "the app offering the endpoint isn't answering right now",
    503: "the app offering the endpoint isn't answering right now",
    504: "the app offering the endpoint didn't answer in time",
}


def pool(path):
    # The path is appended to the address without a leading "/"; the key goes in the header on every request.
    request = urllib.request.Request(ADDRESS + path, headers={"Authorization": f"Bearer {KEY}"})
    try:
        return urllib.request.urlopen(request, timeout=60)
    except urllib.error.HTTPError as error:
        explanation = EXPLANATION.get(error.code) or error.read(300).decode("utf-8", "replace")
        raise RuntimeError(f"{error.code}, {explanation}") from None


class App(BaseHTTPRequestHandler):
    def _text(self, code, text):
        self.send_response(code)
        self.send_header("Content-Type", "text/plain; charset=utf-8")
        self.end_headers()
        self.wfile.write(text.encode())

    def do_GET(self):
        if not (ADDRESS and KEY):
            return self._text(503, "The environment isn't connected to the endpoint or wasn't redeployed after connecting.\n")
        try:
            with pool("merhaba") as answer:
                data = json.load(answer)
            stream = pool("akis")
        except (RuntimeError, OSError) as error:
            return self._text(502, f"API pool: {error}\n")
        with stream:
            self._text(200, f"{data['message']}, caller: {data['caller']}\n")
            for line in stream:  # the streaming response is passed on line by line, as it arrives
                self.wfile.write(line)
                self.wfile.flush()


ThreadingHTTPServer(("0.0.0.0", int(os.environ.get("PORT", "8080"))), App).serve_forever()

To try it, deploy the provider and add its endpoint to the pool as ornek-api; connect the consumer's environment to this endpoint, redeploy it and open the consumer's site. The lines arrive a second apart; the "caller" value on the first line is the X-Turguz-Tuketici header the provider saw.

#Opening an endpoint to the internet

An endpoint with İnternet (Internet) visibility is called from outside Turguz, at the endpoint's external address. The external address is shown on the endpoint's row and can be copied. You don't need to write your own authentication: the platform checks the key. The external client also sends the key in the Authorization: Bearer <key> header.

You choose who may reach the external address in the Dış adrese kimler ulaşır (Who reaches the external address) section:

  • Yalnız seçtiğim adres listeleri (Only the address lists I choose): from your tenant's allowed address lists. A request from an address not on the lists is rejected.
  • Anahtarı olan herkes, her adresten (Anyone with a key, from any address): needs a separate confirmation. Some installations don't offer this option; the endpoint can only be opened to address lists.

When you first open the endpoint to the internet, nothing is chosen: until you choose an address list, no address can reach the external address. «Anahtarı olan herkes» (Anyone with a key) is never chosen for you; it opens only when you choose and confirm it.

«Dış anahtar oluştur» (Create external key) gives a key to your external client (for example your mobile app):

  • The key is shown once; copy it and press «Kopyaladım, kapat» (Copied, close). It's never shown again.
  • Every external key appears under its own name in the «Bağlantılar» (Connections) window and can be closed one by one.
  • When you renew it, the old key stops working immediately or, if you choose, keeps working for a short while.
  • When the endpoint is taken off the internet, its external keys are closed.
  • An external key works only on the external address; the internal address your environments use rejects it. Your environments' keys don't work on the external address either.
  • Don't put an external key in code that runs in a browser: anyone who opens the page sees the key. The endpoint doesn't accept browser calls from another site anyway.
  • The external address works only over https://; a request over http:// is rejected.
  • A response from the external address cannot leave cookies in the browser and does not run as a page. Do not serve an app that keeps sessions from it; the external address is for data.

#Error codes

These are the codes the pool itself returns. Codes returned by the provider's app go to the consumer unchanged.

CodeMeaning
401The key is missing, wrong or expired, or the connection has been closed. If the connection was just opened or the key just renewed, try again shortly.
403On an endpoint open to the internet, the request's address isn't on the chosen lists.
404No such endpoint, or it can't be called this way (for example the endpoint is no longer open to the internet).
413The request body is over the endpoint's limit. Make the body smaller or ask the provider to raise the limit.
429Too many requests: the key's limit was exceeded. Wait as long as the response says and try again.
502, 503, 504The app offering the endpoint isn't answering right now (stopped, restarting or too slow to respond). The response body carries a short JSON explanation.

If the request doesn't reach the pool at all (the connection can't be made), first check on the environment's API havuzu (API pool) tab that the connection is open; if it is, try again a little later, and if it persists, tell your platform admin.

#With an assistant

A connected AI assistant (Connect to your assistant) uses the pool with the same permissions as the panel: it lists endpoints, connects, closes connections, renews keys and changes an endpoint's settings. For steps such as closing a connection, removing an endpoint, narrowing visibility or opening an endpoint to the internet, the assistant asks for your confirmation first.

  • A cloud assistant can't approve connection requests; approve them in the panel or with the assistant on your own computer.
  • An assistant never sees an external key's value. A cloud assistant can't create or renew an external key; the assistant on your own computer can create one, but isn't shown its value.
  • Get an external key's value in the panel: someone who can manage the project renews the key in the «Bağlantılar» (Connections) window and the new value is shown once.