Skip to main content

Services

Services let one project call another project's hooks from its code. The project that is called is the service; the project that calls it is the caller. The service decides which of its hooks can be called (its actions) and which projects may call each one, with the same roles you already use in Access Control.

There are no tokens or connections to manage: the service's managers give the calling project a role, the service shows up in the caller's Services page, and the caller's code runs the action with one function.

from abstra.services import run_service_action

invoice = run_service_action(
service_id="2c051024-fd14-441b-9d11-a6bc7d1c94c0", # the service's project id
action="create-invoice",
parameters={"customer_id": "c-42", "amount": 199.9},
)

How it works​

  1. The service declares actions. An action is a hook that declares an api_schema: a description, the JSON Schema of the parameters it accepts and, optionally, of what it answers. See Declaring actions.
  2. The service gives the caller access. Public hooks can be called by any project, as they can be called by anyone over HTTP. For a private hook, the service's managers give the calling project one of the roles the hook requires.
  3. The caller runs the action. run_service_action validates the parameters against the action's schema, checks the caller's access, runs the hook in the service's production deploy and returns the hook's answer.

Giving a project access​

In the service project's console:

  1. Open Access Control → Roles and pick (or create) the role the hook requires.
  2. In Who has this role, search for the calling project's name. Projects appear under Projects.
  3. Select it. The project shows as a chip on the role, and you can remove it the same way. In the calling project, your project now shows up under Services.

The roles a project holds work exactly like a person's: the project can run every private action whose roles include one of them. To make a hook require a role, set it to private with that role in Access Control → Stages.

Giving access to a project of another organization​

A project of another organization gets roles through an invite:

Availability

Invites to other organizations are being rolled out by organization. If you don't see Invite a project from another organization on a role, contact support to enable it.

  1. In the service's Access Control → Roles, pick the role and click Invite a project from another organization.
  2. Enter the email of a person in the other organization and send. They get an email with a link that is valid for 7 days.
  3. They open the link, sign in with that email and choose which of their projects receives the access (any project they can edit). They see the service and who invited them, but not the role.
  4. Once they accept, their project shows on the role as Project · Organization. On their side, your project shows up in its Services, and its code can run your actions like a project of your own organization.

Under the role you see each invite's status (pending, accepted, declined, expired) and can Resend it (a new link, valid for another 7 days; the old one stops working) or Revoke it while it's pending. After it's accepted, you can add or remove that project's roles like any other. If you remove all of them, the project needs a new invite to get access again.

When a project moves to another organization, the access between it and projects that stayed behind is removed, and its pending invites are revoked: grant or invite again from its new organization.

Seeing what a project can call​

In the calling project's console, Services lists the services the project holds a role on. Open a service to see the actions this project may run; open an action to see its parameters, its answer and a code snippet to call it.

From code, the same information comes from list_services, list_service_actions and describe_service_action (see the SDK reference). Smart Chat and MCP clients have matching tools; Smart Chat always asks you before running an action.

Calling an action​

from abstra.services import ServiceActionError, run_service_action

try:
quote = run_service_action(
BILLING_SERVICE_ID,
"quote-order",
{"customer": {"name": "Ana", "email": "ana@example.com"}, "items": [...], "shipping": {"method": "pickup"}},
)
except ServiceActionError as error:
print(error.status, error.code, error.details)
  • The call runs the service's production deploy, from the editor too: test with care when an action writes data.
  • It returns the hook's response body, parsed as JSON when it is JSON.
  • It raises ServiceActionError when the call is refused, or when the hook answers with an error status (400 or above). See Errors.

What the service's hook receives​

The hook runs as if it received a POST with the parameters as its JSON body, so get_request() works as usual. It also receives who is calling:

HeaderValue
x-abstra-caller-project-idThe calling project's id
x-abstra-caller-project-nameThe calling project's name
x-abstra-caller-organization-idThe calling project's organization
x-abstra-call-depthHow many cross-project calls led here (1 for a direct call)

These headers are only set by Abstra: they are removed from any plain HTTP request to the hook, so the hook can trust them.

import abstra.hooks as ah

body, query, headers = ah.get_request()
caller = headers.get("x-abstra-caller-project-name")

Limits​

LimitValue
Calls from one project to one service120 per minute
Calls from one project to one service running at the same time10
Chain of calls (A calls B, which calls C…)8 deep

Past a limit, run_service_action raises ServiceActionError with status 429 (rate_limited) or 508 (call_depth_exceeded). The executions count against the service's organization, like any hook execution.

Renamed from Connected Apps​

This feature used to be called Connected Apps, and its module abstra.apps (run_app_action, list_connected_apps, list_app_actions, describe_app_action, AppActionError). That module still works, with a deprecation warning: move your code to abstra.services.