Try Opteryx

Control API

Base URL: https://control.opteryx.app

Overview

Billing accounts and membership, payment methods and invoices, workspace lifecycle and catalogs, and the notification feed behind the Studio bell. Access policies are granted in SQL rather than here — see GRANT and REVOKE.

Endpoints

ServiceDocs
Bootstrap a new workspace's initial policiespost/v1/access/workspace/{workspace}/genesis View
List Accountsget/v1/accounts View
Create Accountpost/v1/accounts View
Get Accountget/v1/accounts/{account_id} View
Update Accountpatch/v1/accounts/{account_id} View
Delete Accountdelete/v1/accounts/{account_id} View
List Domainsget/v1/accounts/{account_id}/domains View
Claim Domainpost/v1/accounts/{account_id}/domains View
Release Domaindelete/v1/accounts/{account_id}/domains/{domain} View
Verify Domainpost/v1/accounts/{account_id}/domains/{domain}/verify View
Get Idp Policyget/v1/accounts/{account_id}/idp-policy View
Put Idp Policyput/v1/accounts/{account_id}/idp-policy View
Delete Idp Policydelete/v1/accounts/{account_id}/idp-policy View
List Denialsget/v1/accounts/{account_id}/idp-policy/denials View
Preview Idp Policypost/v1/accounts/{account_id}/idp-policy/preview View
List Membersget/v1/accounts/{account_id}/members View
Invite Memberpost/v1/accounts/{account_id}/members View
Update Member Rolepatch/v1/accounts/{account_id}/members/{identity} View
Remove Memberdelete/v1/accounts/{account_id}/members/{identity} View
Accept Invitepost/v1/accounts/{account_id}/members/{identity}/accept View
Get Payment Methodget/v1/accounts/{account_id}/payment-methods View
Attach Payment Methodpost/v1/accounts/{account_id}/payment-methods View
Detach Payment Methoddelete/v1/accounts/{account_id}/payment-methods View
Create Paymentpost/v1/accounts/{account_id}/payments View
List Service Accountsget/v1/accounts/{account_id}/service-accounts View
Add Service Accountpost/v1/accounts/{account_id}/service-accounts View
Remove Service Accountdelete/v1/accounts/{account_id}/service-accounts/{identity} View
List Account Workspacesget/v1/accounts/{account_id}/workspaces View
Test Draft Catalog Connectionpost/v1/catalog-connections/test View
Get Catalog Kindsget/v1/catalog-kinds View
List Invoicesget/v1/invoices View
Get Invoiceget/v1/invoices/{invoice_id} View
List notificationsget/v1/notifications View
Mark all notifications readpost/v1/notifications/read-all View
Dismiss a notificationpost/v1/notifications/{notification_id}/dismiss View
Mark a notification readpost/v1/notifications/{notification_id}/read View
Create Workspaceput/v1/workspaces/{name} View
Get Catalog Bindingget/v1/workspaces/{name}/catalog View
Put Catalog Bindingput/v1/workspaces/{name}/catalog View
Delete Catalog Bindingdelete/v1/workspaces/{name}/catalog View
Sync Catalog Dataset Listpost/v1/workspaces/{name}/catalog/sync View
Test Saved Catalog Connectionpost/v1/workspaces/{name}/catalog/test View
Get Workspace Guard Propertiesget/v1/workspaces/{name}/properties View

Bootstrap a new workspace's initial policies

Request: post/v1/access/workspace/{workspace}/genesis

Tags: Access Control

Create the initial set of access policies for a brand-new workspace, one policy per {identity, role} pair, each scoped to the whole workspace (pattern {workspace}.*). This is a trusted bootstrap operation for whatever creates the workspace record in the first place (e.g. billing.opteryx's workspace-create endpoint) to hand it an explicit member list at creation time, rather than every caller becoming sole owner. Refuses with 409 if the workspace already has any policy at all -- this can only be used once, to bootstrap a workspace that doesn't have policies yet, not to add owners to one that already does.

Path Parameters

  • workspace string [path; required] Workspace name

Header Parameters

  • authorization string | null [header; optional]

Request Body

  • Content-Type: application/json Schema: GenesisGrantRequest
    • grants array<GenesisGrant> [required] Identity/role pairs to grant over the whole workspace

Responses

  • 201 — Successful Response (application/json GenesisGrantResponse)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

post https://control.opteryx.app/v1/access/workspace/{workspace}/genesis
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Path parameters
workspacestring · required
Request body application/json · GenesisGrantRequest

    

List Accounts

Request: get/v1/accounts

Tags: Accounts

List accounts the caller is an active member of.

Membership is exclusive (find_existing_membership), so this returns at most one row in practice - still a list per the design doc's shape, not collapsed to a single object.

Header Parameters

  • authorization string | null [header; optional]

Responses

  • 200 — Successful Response (application/json array<object>)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

get https://control.opteryx.app/v1/accounts
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.

    

Create Account

Request: post/v1/accounts

Tags: Accounts

Create a billing account. The caller becomes its first billing_admin immediately as an active member (not a pending invite) - see new_genesis_member_doc's docstring for why.

Header Parameters

  • authorization string | null [header; optional]

Request Body

  • Content-Type: application/json Schema: AccountCreateRequest
    • name string [required]
    • tax_id string | null [optional]
    • address object | null [optional]

Responses

  • 201 — Successful Response (application/json object)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

post https://control.opteryx.app/v1/accounts
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Request body application/json · AccountCreateRequest

    

Get Account

Request: get/v1/accounts/{account_id}

Tags: Accounts

Read. Only visible to active members of the account - an account's name/tax_id/address are not public to any authenticated caller who happens to know or guess an account id.

Path Parameters

  • account_id string [path; required]

Header Parameters

  • authorization string | null [header; optional]

Responses

  • 200 — Successful Response (application/json object)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

get https://control.opteryx.app/v1/accounts/{account_id}
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Path parameters
account_idstring · required

    

Update Account

Request: patch/v1/accounts/{account_id}

Tags: Accounts

Partial update: name, tax_id, address, member_min_age_ms. Only billing_admins of the account may update it.

Path Parameters

  • account_id string [path; required]

Header Parameters

  • authorization string | null [header; optional]

Request Body

  • Content-Type: application/json Schema: AccountUpdateRequest
    • name string | null [optional]
    • tax_id string | null [optional]
    • address object | null [optional]
    • member_min_age_ms integer | null [optional]

Responses

  • 200 — Successful Response (application/json object)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

patch https://control.opteryx.app/v1/accounts/{account_id}
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Path parameters
account_idstring · required
Request body application/json · AccountUpdateRequest

    

Delete Account

Request: delete/v1/accounts/{account_id}

Tags: Accounts

Soft-delete: status: deactivated. Only billing_admins may call this.

409 if any workspace still references this account. On success, cascades to delete every member subdocument - no "deactivated but still has members" state should exist afterward.

Path Parameters

  • account_id string [path; required]

Header Parameters

  • authorization string | null [header; optional]

Responses

  • 200 — Successful Response (application/json object)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

delete https://control.opteryx.app/v1/accounts/{account_id}
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Path parameters
account_idstring · required

    

List Domains

Request: get/v1/accounts/{account_id}/domains

Tags: Domains

List claimed domains and their verification state.

Path Parameters

  • account_id string [path; required]

Header Parameters

  • authorization string | null [header; optional]

Responses

  • 200 — Successful Response (application/json object)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

get https://control.opteryx.app/v1/accounts/{account_id}/domains
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Path parameters
account_idstring · required

    

Claim Domain

Request: post/v1/accounts/{account_id}/domains

Tags: Domains

Claim a domain, returning the TXT record to publish.

Idempotent: re-claiming a domain this account already holds returns the same token and leaves any existing verification intact, rather than resetting it. Claiming a domain another account has verified is a 409 - an unverified claim elsewhere is not an obstacle, since it proves nothing.

Path Parameters

  • account_id string [path; required]

Header Parameters

  • authorization string | null [header; optional]

Request Body

  • Content-Type: application/json Schema: DomainClaimRequest
    • domain string [required]

Responses

  • 201 — Successful Response (application/json object)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

post https://control.opteryx.app/v1/accounts/{account_id}/domains
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Path parameters
account_idstring · required
Request body application/json · DomainClaimRequest

    

Release Domain

Request: delete/v1/accounts/{account_id}/domains/{domain}

Tags: Domains

Release a claimed domain.

Note this can widen who may sign up - a domain that was routing new @customer.com users into this account stops doing so - which is why it is billing_admin-only and audited.

Path Parameters

  • account_id string [path; required]
  • domain string [path; required]

Header Parameters

  • authorization string | null [header; optional]

Responses

  • 200 — Successful Response (application/json object)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

delete https://control.opteryx.app/v1/accounts/{account_id}/domains/{domain}
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Path parameters
account_idstring · required
domainstring · required

    

Verify Domain

Request: post/v1/accounts/{account_id}/domains/{domain}/verify

Tags: Domains

Check DNS for the expected TXT record and mark the domain verified.

A DNS failure and a missing record are reported differently on purpose: "we could not resolve this" is an operator's cue to wait and retry, while "we resolved it and the record is not there" is a cue to go and publish it. Collapsing both into one message is how domain verification gets a reputation for being flaky.

Path Parameters

  • account_id string [path; required]
  • domain string [path; required]

Header Parameters

  • authorization string | null [header; optional]

Responses

  • 200 — Successful Response (application/json object)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

post https://control.opteryx.app/v1/accounts/{account_id}/domains/{domain}/verify
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Path parameters
account_idstring · required
domainstring · required

    

Get Idp Policy

Request: get/v1/accounts/{account_id}/idp-policy

Tags: IDP Policy

Read the account's sign-in requirements.

Any active member may read: knowing the rules you must satisfy to sign in is not privileged, and hiding them from members only produces support tickets. Changing them is billing_admin-only.

Path Parameters

  • account_id string [path; required]

Header Parameters

  • authorization string | null [header; optional]

Responses

  • 200 — Successful Response (application/json object)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

get https://control.opteryx.app/v1/accounts/{account_id}/idp-policy
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Path parameters
account_idstring · required

    

Put Idp Policy

Request: put/v1/accounts/{account_id}/idp-policy

Tags: IDP Policy

Replace the account's sign-in requirements.

Whole-document replace, not a patch: a policy that refuses sign-ins should be read in full at the point of change, not assembled from a diff against something the operator may not have looked at.

version is bumped on every write. A refresh session issued under a lower version is refused at mint and sent back through interactive sign-in, so tightening a policy cannot be outlived by sessions that predate it.

Path Parameters

  • account_id string [path; required]

Query Parameters

  • force boolean [query; optional] Proceed even if this would deny your own sign-in Default: false

Header Parameters

  • authorization string | null [header; optional]

Request Body

  • Content-Type: application/json Schema: IdpPolicyRequest
    • enforced boolean [optional] Default: false
    • providers array<string> [optional]
    • microsoft MicrosoftPolicy [optional]
    • google GooglePolicy [optional]
    • allow_password_auth boolean [optional] Default: true
    • machine_credentials MachineCredentialsPolicy [optional]
    • require_mfa boolean [optional] Default: false
    • max_auth_age_hours integer | null [optional]
    • max_session_hours integer | null [optional]
    • email_domains array<string> [optional]

Responses

  • 200 — Successful Response (application/json object)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

put https://control.opteryx.app/v1/accounts/{account_id}/idp-policy
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Path parameters
account_idstring · required
Query parameters
forceboolean · optional
Request body application/json · IdpPolicyRequest

    

Delete Idp Policy

Request: delete/v1/accounts/{account_id}/idp-policy

Tags: IDP Policy

Remove the account's sign-in requirements entirely.

Deliberately unguarded, unlike the write path: removing a restriction can only widen who may sign in, so it cannot lock anybody out. It still needs a fresh interactive session, because turning the control off is exactly what an attacker holding a stale machine credential would want to do.

Path Parameters

  • account_id string [path; required]

Header Parameters

  • authorization string | null [header; optional]

Responses

  • 200 — Successful Response (application/json object)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

delete https://control.opteryx.app/v1/accounts/{account_id}/idp-policy
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Path parameters
account_idstring · required

    

List Denials

Request: get/v1/accounts/{account_id}/idp-policy/denials

Tags: IDP Policy

Recent sign-ins this account's policy refused.

Without this, a customer's admin cannot answer "why can't Bob sign in" and every denial becomes a support request that needs someone to read our logs. It is the endpoint most likely to be dropped for time and the one that most determines whether the feature is operable.

Denials are emitted to the audit stream by authenticate.opteryx at mint (app/idp_policy.py::audit_decision); this reads them back from the account's idp_denials subcollection, which the log pipeline populates. Returns an empty list rather than 404 when nothing has been recorded - "no denials" is the normal, healthy state and must not read as an error.

Path Parameters

  • account_id string [path; required]

Query Parameters

  • limit integer [query; optional] Default: 50

Header Parameters

  • authorization string | null [header; optional]

Responses

  • 200 — Successful Response (application/json object)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

get https://control.opteryx.app/v1/accounts/{account_id}/idp-policy/denials
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Path parameters
account_idstring · required
Query parameters
limitinteger · optional

    

Preview Idp Policy

Request: post/v1/accounts/{account_id}/idp-policy/preview

Tags: IDP Policy

Report who a candidate policy would admit, writing nothing.

This is what makes enabling a policy something other than a blind lockout - an admin needs to see the three contractors who sign in with Google before they hear about it from a support ticket. The UI calls this on every change and blocks the save button on its result.

Path Parameters

  • account_id string [path; required]

Header Parameters

  • authorization string | null [header; optional]

Request Body

  • Content-Type: application/json Schema: IdpPolicyRequest
    • enforced boolean [optional] Default: false
    • providers array<string> [optional]
    • microsoft MicrosoftPolicy [optional]
    • google GooglePolicy [optional]
    • allow_password_auth boolean [optional] Default: true
    • machine_credentials MachineCredentialsPolicy [optional]
    • require_mfa boolean [optional] Default: false
    • max_auth_age_hours integer | null [optional]
    • max_session_hours integer | null [optional]
    • email_domains array<string> [optional]

Responses

  • 200 — Successful Response (application/json object)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

post https://control.opteryx.app/v1/accounts/{account_id}/idp-policy/preview
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Path parameters
account_idstring · required
Request body application/json · IdpPolicyRequest

    

List Members

Request: get/v1/accounts/{account_id}/members

Tags: Members

List members. Any active member of the account may list.

Expired pending rows (now > invite_expires_at) are filtered out of the response and lazily deleted from Firestore when encountered - no separate sweep job, per api-v2.md.

Path Parameters

  • account_id string [path; required]

Header Parameters

  • authorization string | null [header; optional]

Responses

  • 200 — Successful Response (application/json array<object>)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

get https://control.opteryx.app/v1/accounts/{account_id}/members
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Path parameters
account_idstring · required

    

Invite Member

Request: post/v1/accounts/{account_id}/members

Tags: Members

Invite a new member. Only existing billing_admins of this account may invite.

409 if the invitee already has an active/pending membership on another account (checked via the collection-group find_existing_membership query). Re-inviting an identity that's still pending on this same account resets the 7-day clock instead of erroring - a fresh new_member_doc is written over the existing pending doc.

Path Parameters

  • account_id string [path; required]

Header Parameters

  • authorization string | null [header; optional]

Request Body

  • Content-Type: application/json Schema: InviteMemberRequest
    • identity string [required]
    • email string [required]
    • role string [required]

Responses

  • 201 — Successful Response (application/json object)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

post https://control.opteryx.app/v1/accounts/{account_id}/members
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Path parameters
account_idstring · required
Request body application/json · InviteMemberRequest

    

Update Member Role

Request: patch/v1/accounts/{account_id}/members/{identity}

Tags: Members

{role: "billing_admin"|"member"}. Only billing_admins may change roles.

Path Parameters

  • account_id string [path; required]
  • identity string [path; required]

Header Parameters

  • authorization string | null [header; optional]

Request Body

  • Content-Type: application/json Schema: UpdateMemberRoleRequest
    • role string [required]

Responses

  • 200 — Successful Response (application/json object)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

patch https://control.opteryx.app/v1/accounts/{account_id}/members/{identity}
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Path parameters
account_idstring · required
identitystring · required
Request body application/json · UpdateMemberRoleRequest

    

Remove Member

Request: delete/v1/accounts/{account_id}/members/{identity}

Tags: Members

Remove a member (also how an unaccepted invite gets withdrawn).

Only billing_admins may remove other members. Self-removal (an identity removing itself, i.e. leaving the account) is allowed without the billing_admin check - api-v2.md is silent on this specifically, but it's a low-risk, clearly-reasonable capability (a member/admin should always be able to leave their own account).

Path Parameters

  • account_id string [path; required]
  • identity string [path; required]

Header Parameters

  • authorization string | null [header; optional]

Responses

  • 200 — Successful Response (application/json object)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

delete https://control.opteryx.app/v1/accounts/{account_id}/members/{identity}
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Path parameters
account_idstring · required
identitystring · required

    

Accept Invite

Request: post/v1/accounts/{account_id}/members/{identity}/accept

Tags: Members

Invitee (authenticated as {identity}) flips pending -> active.

Stamps added_at/min_age_ms/eligible_at at acceptance time - min_age_ms is read fresh from the account doc now, not from whatever was on the invite, per api-v2.md's "snapshot at accept time" behavior. 410 if the invite has expired; 404 if there's no pending invite for this identity on this account.

Path Parameters

  • account_id string [path; required]
  • identity string [path; required]

Header Parameters

  • authorization string | null [header; optional]

Responses

  • 200 — Successful Response (application/json object)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

post https://control.opteryx.app/v1/accounts/{account_id}/members/{identity}/accept
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Path parameters
account_idstring · required
identitystring · required

    

Get Payment Method

Request: get/v1/accounts/{account_id}/payment-methods

Tags: Payment Methods

Return the single attached payment method, or null if none is attached.

A bare null (rather than e.g. a 404) is used because "no payment method attached" is a normal, expected state for this resource - not an error - matching how billing_account: null is treated as a normal free-tier state elsewhere in this design.

Path Parameters

  • account_id string [path; required]

Header Parameters

  • authorization string | null [header; optional]

Responses

  • 200 — Successful Response (application/json object | null)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

get https://control.opteryx.app/v1/accounts/{account_id}/payment-methods
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Path parameters
account_idstring · required

    

Attach Payment Method

Request: post/v1/accounts/{account_id}/payment-methods

Tags: Payment Methods

Attach a Stripe payment method, replacing whatever's already attached.

Verifies the token against Stripe's API before trusting it, then - if a different payment method was already attached - detaches the old one on Stripe's side so it isn't left orphaned there.

Path Parameters

  • account_id string [path; required]

Header Parameters

  • authorization string | null [header; optional]

Request Body

  • Content-Type: application/json Schema: AttachPaymentMethodRequest
    • stripe_payment_method_id string [required]
    • brand string [required]
    • last4 string [required]

Responses

  • 200 — Successful Response (application/json object)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

post https://control.opteryx.app/v1/accounts/{account_id}/payment-methods
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Path parameters
account_idstring · required
Request body application/json · AttachPaymentMethodRequest

    

Detach Payment Method

Request: delete/v1/accounts/{account_id}/payment-methods

Tags: Payment Methods

Detach the account's payment method, if any. Idempotent - detaching when nothing is attached is a no-op 204, not an error.

Path Parameters

  • account_id string [path; required]

Header Parameters

  • authorization string | null [header; optional]

Responses

  • 204 — Successful Response
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

delete https://control.opteryx.app/v1/accounts/{account_id}/payment-methods
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Path parameters
account_idstring · required

    

Create Payment

Request: post/v1/accounts/{account_id}/payments

Tags: Payment Methods

Ad hoc charge against the account's attached payment method.

400 if no payment method is attached. A real PaymentIntent is created and confirmed synchronously against the attached Stripe payment method id - this moves real money, so this deliberately does not fake a successful response; a Stripe failure surfaces as 402.

Path Parameters

  • account_id string [path; required]

Header Parameters

  • authorization string | null [header; optional]

Request Body

  • Content-Type: application/json Schema: CreatePaymentRequest
    • amount integer [required]
    • currency string [optional] Default: usd
    • description string | null [optional]

Responses

  • 201 — Successful Response (application/json object)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

post https://control.opteryx.app/v1/accounts/{account_id}/payments
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Path parameters
account_idstring · required
Request body application/json · CreatePaymentRequest

    

List Service Accounts

Request: get/v1/accounts/{account_id}/service-accounts

Tags: Service Accounts

List the service accounts this billing account holds, and the quota.

Any active member may list. The quota rides along so a caller does not have to know the plan table to render "3 of 5 used", and cannot drift from what the add route actually enforces.

Path Parameters

  • account_id string [path; required]

Header Parameters

  • authorization string | null [header; optional]

Responses

  • 200 — Successful Response (application/json object)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

get https://control.opteryx.app/v1/accounts/{account_id}/service-accounts
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Path parameters
account_idstring · required

    

Add Service Account

Request: post/v1/accounts/{account_id}/service-accounts

Tags: Service Accounts

Claim a service account seat on this billing account.

Any active member may add one, not only a billing_admin. It consumes quota, which is a billing-relevant act - but it is also the ordinary way a developer gives a CI job an identity, and routing every one of those through a billing admin would make the feature unusable on exactly the team accounts that pay for it. The row records who did it.

402, not 403, when the plan has no room: the caller is permitted, the plan is what is refusing, and the difference is the whole point of the feature being an unlock.

Path Parameters

  • account_id string [path; required]

Header Parameters

  • authorization string | null [header; optional]

Request Body

  • Content-Type: application/json Schema: AddServiceAccountRequest
    • identity string [required]

Responses

  • 201 — Successful Response (application/json object)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

post https://control.opteryx.app/v1/accounts/{account_id}/service-accounts
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Path parameters
account_idstring · required
Request body application/json · AddServiceAccountRequest

    

Remove Service Account

Request: delete/v1/accounts/{account_id}/service-accounts/{identity}

Tags: Service Accounts

Release a service account's seat.

Called by authenticate.opteryx when the service account is deleted, and by hand to detach one. Deliberately idempotent-ish: a missing row is a 404, but removing a row for an identity that no longer exists elsewhere is fine - this route does not go looking.

Any active member may remove one, matching who may add. Note this is NOT reachable by the service account itself: _require_active_member refuses a service_account row, so a machine identity cannot detach itself from the bill it runs up.

Path Parameters

  • account_id string [path; required]
  • identity string [path; required]

Header Parameters

  • authorization string | null [header; optional]

Responses

  • 200 — Successful Response (application/json object)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

delete https://control.opteryx.app/v1/accounts/{account_id}/service-accounts/{identity}
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Path parameters
account_idstring · required
identitystring · required

    

List Account Workspaces

Request: get/v1/accounts/{account_id}/workspaces

Tags: Workspaces

Manage-workspaces listing. Caller must be an active member of the account (any role).

A genuine two-database read per workspace: this service's own workspaces/{name} doc for billing_account/members, plus the catalogs database's $properties doc (via an OpteryxCatalog handle) for lock state and the two protection guards.

DROP WORKSPACE removes the catalog's $properties doc outright - it has no reason to touch this service's own workspaces/{name} registration doc (billing_account/members are this service's own concern, not the catalog's) - so a row whose properties read comes back empty names a workspace that no longer exists; it is skipped rather than listed.

Path Parameters

  • account_id string [path; required]

Header Parameters

  • authorization string | null [header; optional]

Responses

  • 200 — Successful Response (application/json array<object>)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

get https://control.opteryx.app/v1/accounts/{account_id}/workspaces
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Path parameters
account_idstring · required

    

Test Draft Catalog Connection

Request: post/v1/catalog-connections/test

Tags: Workspace Catalog

Test a catalog that no workspace uses yet.

The primary test, because the catalog choice is permanent: a wrong address or an unusable credential has to surface while it can still be corrected, not at the first query against a workspace that can only be thrown away.

ok: false is a 200. The request succeeded; the CATALOG failed, and squeezing that into an HTTP status makes the caller guess whether a 401 came from us or from them.

Header Parameters

  • authorization string | null [header; optional]

Request Body

  • Content-Type: application/json Schema: CatalogTestRequest
    • kind string [required]
    • config object [optional] Default: {}
    • auth CatalogAuthRequest [optional]

Responses

  • 200 — Successful Response (application/json object)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

post https://control.opteryx.app/v1/catalog-connections/test
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Request body application/json · CatalogTestRequest

    

Get Catalog Kinds

Request: get/v1/catalog-kinds

The kind descriptor and this deployment's capabilities.

Cached privately for five minutes with an ETag over the body: it is per-deployment static, a create form fetches it before it can render, and a capability that flips does so on a deploy, not mid-session.

Header Parameters

  • authorization string | null [header; optional]

Responses

  • 200 — Successful Response (application/json object)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

get https://control.opteryx.app/v1/catalog-kinds
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.

    

List Invoices

Request: get/v1/invoices

Tags: Invoices

Placeholder: return an empty invoices list.

Header Parameters

  • authorization string | null [header; optional]

Responses

  • 200 — Successful Response (application/json array<object>)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

get https://control.opteryx.app/v1/invoices
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.

    

Get Invoice

Request: get/v1/invoices/{invoice_id}

Tags: Invoices

Placeholder: return a minimal invoice representation.

Path Parameters

  • invoice_id string [path; required]

Header Parameters

  • authorization string | null [header; optional]

Responses

  • 200 — Successful Response (application/json object)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

get https://control.opteryx.app/v1/invoices/{invoice_id}
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Path parameters
invoice_idstring · required

    

List notifications

Request: get/v1/notifications

Tags: Notifications

The caller's own notification feed, newest first, with the unread count.

Query Parameters

  • limit integer [query; optional] Default: 50

Header Parameters

  • authorization string | null [header; optional]

Responses

  • 200 — Successful Response (application/json object)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

get https://control.opteryx.app/v1/notifications
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Query parameters
limitinteger · optional

    

Mark all notifications read

Request: post/v1/notifications/read-all

Tags: Notifications

Marks every unread notification in the caller's feed as read.

Header Parameters

  • authorization string | null [header; optional]

Responses

  • 200 — Successful Response (application/json object)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

post https://control.opteryx.app/v1/notifications/read-all
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.

    

Dismiss a notification

Request: post/v1/notifications/{notification_id}/dismiss

Tags: Notifications

Removes one of the caller's own notifications from the feed.

Path Parameters

  • notification_id string [path; required]

Header Parameters

  • authorization string | null [header; optional]

Responses

  • 200 — Successful Response (application/json object)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

post https://control.opteryx.app/v1/notifications/{notification_id}/dismiss
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Path parameters
notification_idstring · required

    

Mark a notification read

Request: post/v1/notifications/{notification_id}/read

Tags: Notifications

Marks one of the caller's own notifications as read.

Path Parameters

  • notification_id string [path; required]

Header Parameters

  • authorization string | null [header; optional]

Responses

  • 200 — Successful Response (application/json object)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

post https://control.opteryx.app/v1/notifications/{notification_id}/read
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Path parameters
notification_idstring · required

    

Create Workspace

Request: put/v1/workspaces/{name}

Tags: Workspaces

Idempotent create. Caller must be an active billing_admin on body.billing_account.

A workspace exists in THREE places, and all three are written here, in a deliberate order:

  1. catalogs/{name}/$properties - the catalog namespace, stamped with billing-account-id. This is what opteryx_catalog means by a workspace; without it every CREATE TABLE in the workspace fails with WorkspaceNotFound no matter what the other two say.
  2. workspaces/{name} in this service's own database - billing_account and members.
  3. The genesis access grants, bootstrapped in-process via _bootstrap_genesis_policies (this was an HTTP call to policy.opteryx before that service merged into this one).

ORDERING. (3) is a one-way door: this service cannot revoke a grant it caused, genesis refuses a self-revoke, and it answers 409 for the rest of time once any policy exists on the name. (1) and (2) are local documents this service can re-read and overwrite. So both reversible writes run first and the irreversible one runs last, and a failure anywhere leaves a state a retry can recognize and finish.

(1) comes first because it is also the claim record. It is the one document that says which billing account holds this name, it is where scripts/reserve_workspace_names.py records a hold, and it is durable before either of the other writes - so a retry after a crash at any point can tell "my own half-finished attempt" from "someone else's name", which is exactly what the code could not do before (see billing-test-suite/ STATUS.md: a genesis timeout stranded a workspace name permanently, because nothing durable recorded the claim until after genesis had already committed).

Failure modes, all recoverable by re-issuing the same PUT:

  • (1) fails: 504, nothing else written, name still free.
  • (2) fails: the namespace is claimed by this billing account and no grants exist. A retry adopts it; another account's PUT is refused.
  • (3) fails: the workspace is visible in the account listing and can be deleted, and a retry re-attempts genesis - a 409 then means the earlier attempt's grants already landed, which is success. This is the trade named in STATUS.md: a workspace briefly without grants, rather than a name permanently without recourse. It carries no charge (billing is metered off usage events, and an empty workspace produces none).

Idempotency: if workspaces/{name} already exists with the same billing_account/members as requested, this is treated as a retry and the existing doc is returned rather than erroring. If the doc exists with different billing_account/members, that's a real name conflict: 409, no genesis bootstrap attempted.

Path Parameters

  • name string [path; required]

Header Parameters

  • authorization string | null [header; optional]

Request Body

  • Content-Type: application/json Schema: WorkspaceCreateRequest
    • billing_account string [required]
    • members array<WorkspaceMemberGrant> [required]
    • catalog CatalogBindingRequest | null [optional]

Responses

  • 200 — Successful Response (application/json object)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

put https://control.opteryx.app/v1/workspaces/{name}
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Path parameters
namestring · required
Request body application/json · WorkspaceCreateRequest

    

Get Catalog Binding

Request: get/v1/workspaces/{name}/catalog

Tags: Workspace Catalog

The workspace's binding, ciphertext redacted; kind "native" if unbound.

Path Parameters

  • name string [path; required]

Header Parameters

  • authorization string | null [header; optional]

Responses

  • 200 — Successful Response (application/json object)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

get https://control.opteryx.app/v1/workspaces/{name}/catalog
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Path parameters
namestring · required

    

Put Catalog Binding

Request: put/v1/workspaces/{name}/catalog

Tags: Workspace Catalog

Create or replace the workspace's binding. See the module docstring for the precondition order and the secret-handling contract.

Path Parameters

  • name string [path; required]

Header Parameters

  • authorization string | null [header; optional]

Request Body

  • Content-Type: application/json Schema: CatalogBindingRequest
    • kind string [required]
    • config object [optional] Default: {}
    • auth CatalogAuthRequest [optional]
    • preserve_sql_case boolean [optional] Default: false

Responses

  • 200 — Successful Response (application/json object)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

put https://control.opteryx.app/v1/workspaces/{name}/catalog
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Path parameters
namestring · required
Request body application/json · CatalogBindingRequest

    

Delete Catalog Binding

Request: delete/v1/workspaces/{name}/catalog

Tags: Workspace Catalog

Remove the binding, reverting the workspace to the native catalog.

Path Parameters

  • name string [path; required]

Header Parameters

  • authorization string | null [header; optional]

Responses

  • 200 — Successful Response (application/json object)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

delete https://control.opteryx.app/v1/workspaces/{name}/catalog
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Path parameters
namestring · required

    

Sync Catalog Dataset List

Request: post/v1/workspaces/{name}/catalog/sync

Tags: Workspace Catalog

Re-list the workspace's external catalog into stub dataset documents.

Owner-or-admin, not owner-only: the request carries nothing, so an admin can refresh and diagnose a listing without being able to change where the workspace's data comes from. See the module docstring for the tiering rule and for why nothing may call this on a user's behalf.

Order matters. Every external round trip happens before the first write, so an unreachable catalog leaves the stored listing exactly as it was and the 502/504 can say so without hedging.

What it costs the customer's catalog: one list_tables per namespace, then one table load per TABLE for its schema, then a manifest read per table for row counts and column bounds. That is a great deal more than the name-only listing this started as, and it is the reason the user-initiated-only rule is not a preference. Schema is there because odata's $metadata emits nothing for a dataset with no resolvable columns - a name-only stub was visible in the service document and invisible to Excel and Power BI.

Path Parameters

  • name string [path; required]

Header Parameters

  • authorization string | null [header; optional]

Responses

  • 200 — Successful Response (application/json object)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

post https://control.opteryx.app/v1/workspaces/{name}/catalog/sync
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Path parameters
namestring · required

    

Test Saved Catalog Connection

Request: post/v1/workspaces/{name}/catalog/test

Tags: Workspace Catalog

Test the binding this workspace already has. Body is ignored.

Owner-or-admin: the request carries nothing, and diagnosing a workspace is not the same authority as changing one. A stored credential is decrypted for the probe and never leaves this process.

Path Parameters

  • name string [path; required]

Header Parameters

  • authorization string | null [header; optional]

Responses

  • 200 — Successful Response (application/json object)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

post https://control.opteryx.app/v1/workspaces/{name}/catalog/test
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Path parameters
namestring · required

    

Get Workspace Guard Properties

Request: get/v1/workspaces/{name}/properties

Tags: Workspaces

The workspace's guard flags, for ANY workspace the caller owns.

The account listing above answers the same question, but only for workspaces registered with a billing account - it is driven by where("billing_account", "==", ...), so a workspace that predates billing.opteryx or was created any other way is simply absent from it. That left the UI unable to read its own setting back for those, which is not a state a protection control can be in: a switch that cannot report what it is set to is a switch nobody can trust.

So this is keyed by workspace and nothing else. Authorization comes from the access policies (_check_workspace_access, owner/admin on the workspace itself), NOT from billing-account membership - the same reason the route exists at all, since an unregistered workspace has no billing account to be a member of. The catalog handle is built by name, which needs no billing row either.

Returns the same resolved booleans as the listing, so a caller can use either source without knowing which one answered.

Path Parameters

  • name string [path; required]

Header Parameters

  • authorization string | null [header; optional]

Responses

  • 200 — Successful Response (application/json object)
  • 422 — Validation Error (application/json HTTPValidationError)

Try it live

get https://control.opteryx.app/v1/workspaces/{name}/properties
Bearer token required
Held in this tab only — never stored or logged. See the Authentication API for how to get one.
Path parameters
namestring · required