Opply Integrations API

The Integrations API from Opply — 1 operation(s) for integrations.

Business capability
Procurement Spend Analysis Management BC-500.60

Operations 15

GET /api/v1/integrations/ Api v1 integrations list #
GET /api/v1/integrations/{provider}/ Api v1 integrations retrieve #
DELETE /api/v1/integrations/{provider}/ Api v1 integrations destroy #
POST /api/v1/integrations/{provider}/agent-token/ Api v1 integrations agent token create #
GET /api/v1/integrations/{provider}/callback/ Integrations callback #
POST /api/v1/integrations/{provider}/connect/ Api v1 integrations connect create #
POST /api/v1/integrations/{provider}/verify/ Api v1 integrations verify create #
POST /api/v1/integrations/plaid/disconnect/ Api v1 integrations plaid disconnect create #
POST /api/v1/integrations/plaid/exchange/ Api v1 integrations plaid exchange create #
DELETE /api/v1/integrations/plaid/items/{item_uuid}/ Api v1 integrations plaid items destroy #
POST /api/v1/integrations/plaid/webhook/ Api v1 integrations plaid webhook create #
POST /api/v1/integrations/unleashed/connect/ Api v1 integrations unleashed connect create #
POST /api/v1/integrations/unleashed/disconnect/ Api v1 integrations unleashed disconnect create #
GET /api/v1/integrations/xero/supplier-spend/ Deterministic Xero supplier-spend breakdown for the current buyer #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/opply-integrations-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

opply-integrations-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Opply Integrations API
  version: 0.0.0
tags:
- name: Integrations
paths:
  /api/v1/integrations/:
    get:
      operationId: api_v1_integrations_list
      description: 'Surface for third-party connector listing, connect-initiation, and revoke.


        Tokens are brokered by AgentCore Identity; this viewset only manages the

        ConnectorConnection mirror table and the audit trail.'
      tags:
      - Integrations
      security:
      - tokenAuth: []
      - cookieAuth: []
      responses:
        '200':
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Connector'
          description: ''
      summary: Api v1 integrations list
      x-summary-source: derived
  /api/v1/integrations/{provider}/:
    get:
      operationId: api_v1_integrations_retrieve
      description: 'Surface for third-party connector listing, connect-initiation, and revoke.


        Tokens are brokered by AgentCore Identity; this viewset only manages the

        ConnectorConnection mirror table and the audit trail.'
      parameters:
      - in: path
        name: provider
        schema:
          type: string
          pattern: ^[a-z0-9_-]+$
        required: true
      tags:
      - Integrations
      security:
      - tokenAuth: []
      - cookieAuth: []
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Connector'
          description: ''
      summary: Api v1 integrations retrieve
      x-summary-source: derived
    delete:
      operationId: api_v1_integrations_destroy
      description: 'Surface for third-party connector listing, connect-initiation, and revoke.


        Tokens are brokered by AgentCore Identity; this viewset only manages the

        ConnectorConnection mirror table and the audit trail.'
      parameters:
      - in: path
        name: provider
        schema:
          type: string
          pattern: ^[a-z0-9_-]+$
        required: true
      tags:
      - Integrations
      security:
      - tokenAuth: []
      - cookieAuth: []
      responses:
        '204':
          description: Connector revoked or already disconnected.
        '403':
          description: Caller is not allowed to revoke this connector.
        '405':
          description: Connector is admin-managed (DB-backed); OAuth lifecycle is disabled.
        '502':
          description: Upstream identity broker error.
      summary: Api v1 integrations destroy
      x-summary-source: derived
  /api/v1/integrations/{provider}/agent-token/:
    post:
      operationId: api_v1_integrations_agent_token_create
      description: 'Resolve a third-party access token for an agent runtime.


        Authenticated via the inbound agent JWT (audience

        ``opply-agents``). The subject (user vs company) is derived

        from ``ConnectorDefinition.kind`` — callers never specify it.


        The caller MAY send ``company_uuid`` in the JSON body; if so,

        we use that company after verifying the JWT user is an employee

        of it. If absent, we fall back to ``user.get_company()`` — the

        single-company case Opply assumes today. The check keeps the

        endpoint safe the day multi-company users return.


        Returns a structured ``status`` so the runtime can skip

        gracefully on missing / stale connections without parsing

        HTTP error codes. The token itself is never logged.'
      parameters:
      - in: path
        name: provider
        schema:
          type: string
          pattern: ^[a-z0-9_-]+$
        required: true
      tags:
      - Integrations
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AgentTokenRequest'
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentTokenResponse'
          description: ''
        '401':
          description: Agent JWT present but invalid or expired.
        '403':
          description: Missing or malformed Authorization header (unauthenticated agent request).
        '502':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentTokenErrorResponse'
          description: ''
      summary: Api v1 integrations agent token create
      x-summary-source: derived
  /api/v1/integrations/{provider}/callback/:
    get:
      operationId: integrations_callback
      description: OAuth callback. AgentCore Identity redirects the user's browser here after they complete consent on the third-party provider; we finalize the ConnectorConnection by calling verify and bounce the browser back to the FE settings page with a `result=` query string.
      parameters:
      - in: path
        name: provider
        schema:
          type: string
          pattern: ^[a-z0-9_-]+$
        required: true
      - in: query
        name: session_id
        schema:
          type: string
        description: The `sessionUri` returned by AgentCore Identity on initiate, sent back as a query param after consent.
        required: true
      tags:
      - Integrations
      security:
      - {}
      responses:
        '302':
          description: Redirect to the FE integrations page.
        '405':
          description: Connector is admin-managed (DB-backed); OAuth lifecycle is disabled.
      summary: Integrations callback
      x-summary-source: derived
  /api/v1/integrations/{provider}/connect/:
    post:
      operationId: api_v1_integrations_connect_create
      description: 'Surface for third-party connector listing, connect-initiation, and revoke.


        Tokens are brokered by AgentCore Identity; this viewset only manages the

        ConnectorConnection mirror table and the audit trail.'
      parameters:
      - in: path
        name: provider
        schema:
          type: string
          pattern: ^[a-z0-9_-]+$
        required: true
      tags:
      - Integrations
      security:
      - tokenAuth: []
      - cookieAuth: []
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InitiateResponse'
          description: ''
        '403':
          description: Caller is not allowed to connect this connector.
        '405':
          description: Connector is admin-managed (DB-backed); OAuth lifecycle is disabled.
        '502':
          description: Upstream identity broker error.
      summary: Api v1 integrations connect create
      x-summary-source: derived
  /api/v1/integrations/{provider}/verify/:
    post:
      operationId: api_v1_integrations_verify_create
      description: 'Probe whether this connector''s stored token is still usable for the

        caller''s company, self-healing the row in both directions: a stale

        ``CONNECTED`` row is downgraded to ``NEEDS_RECONNECT`` when the broker

        can no longer resolve a token, and a ``NEEDS_RECONNECT`` row is lifted

        back to ``CONNECTED`` when the broker resolves a live token again.


        The FE calls this before kicking off a connector-driven flow (e.g. the

        onboarding Xero import) so a dead/expired token surfaces a "reconnect"

        CTA up front instead of failing mid-import with a 502. Returns only a

        coarse ``status`` — never the token itself. Resolves the token the same

        way the import does, so this catches exactly what the import would hit.


        Per-tenant connectors are admin-gated (same ``_can_user_act`` rule as

        ``connect`` / ``destroy``) because the probe can mutate the shared

        connection row.'
      parameters:
      - in: path
        name: provider
        schema:
          type: string
          pattern: ^[a-z0-9_-]+$
        required: true
      tags:
      - Integrations
      security:
      - tokenAuth: []
      - cookieAuth: []
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectorVerifyResponse'
          description: ''
        '403':
          description: Caller is not allowed to verify this connector.
        '502':
          description: Connector credential resolver failed.
      summary: Api v1 integrations verify create
      x-summary-source: derived
  /api/v1/integrations/plaid/disconnect/:
    post:
      operationId: api_v1_integrations_plaid_disconnect_create
      description: 'Disconnect Plaid entirely — revoke every Item and the parent connection.


        Backs the single per-provider "Disconnect" CTA on the connector card

        (the generic ``DELETE /integrations/plaid/`` returns 405 for resolver-backed

        connectors). Per-Item revoke lives at ``plaid/items//``.'
      tags:
      - Integrations
      security:
      - tokenAuth: []
      - cookieAuth: []
      responses:
        '204':
          description: All Items revoked (idempotent).
        '403':
          description: Caller is not a company admin.
      summary: Api v1 integrations plaid disconnect create
      x-summary-source: derived
  /api/v1/integrations/plaid/exchange/:
    post:
      operationId: api_v1_integrations_plaid_exchange_create
      description: Shared admin gate for the per-tenant Plaid lifecycle endpoints.
      tags:
      - Integrations
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PlaidExchangeRequest'
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/PlaidExchangeRequest'
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/PlaidExchangeRequest'
        required: true
      security:
      - tokenAuth: []
      - cookieAuth: []
      responses:
        '201':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlaidExchangeResponse'
          description: ''
        '403':
          description: Caller is not a company admin.
        '502':
          description: Plaid rejected the exchange, or the secret could not be stored.
      summary: Api v1 integrations plaid exchange create
      x-summary-source: derived
  /api/v1/integrations/plaid/items/{item_uuid}/:
    delete:
      operationId: api_v1_integrations_plaid_items_destroy
      description: Shared admin gate for the per-tenant Plaid lifecycle endpoints.
      parameters:
      - in: path
        name: item_uuid
        schema:
          type: string
          format: uuid
        required: true
      tags:
      - Integrations
      security:
      - tokenAuth: []
      - cookieAuth: []
      responses:
        '204':
          description: Item revoked or already gone.
        '403':
          description: Caller is not a company admin.
      summary: Api v1 integrations plaid items destroy
      x-summary-source: derived
  /api/v1/integrations/plaid/link-token/:
    post:
      operationId: api_v1_integrations_plaid_link_token_create
      description: Shared admin gate for the per-tenant Plaid lifecycle endpoints.
      tags:
      - Integrations
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PlaidLinkTokenRequest'
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/PlaidLinkTokenRequest'
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/PlaidLinkTokenRequest'
      security:
      - tokenAuth: []
      - cookieAuth: []
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlaidLinkTokenResponse'
          description: ''
        '403':
          description: Caller is not a company admin.
        '404':
          description: Item for update mode not found.
        '502':
          description: Plaid rejected the link-token request.
      summary: Api v1 integrations plaid link token create
      x-summary-source: derived
  /api/v1/integrations/plaid/webhook/:
    post:
      operationId: api_v1_integrations_plaid_webhook_create
      description: 'Unauthenticated Plaid webhook sink. Identity comes from the verified JWT,

        never from ``request.user`` (Plaid carries no session).'
      tags:
      - Integrations
      security:
      - {}
      responses:
        '200':
          description: Webhook accepted (verified).
        '400':
          description: Verification failed — webhook rejected.
      summary: Api v1 integrations plaid webhook create
      x-summary-source: derived
  /api/v1/integrations/unleashed/connect/:
    post:
      operationId: api_v1_integrations_unleashed_connect_create
      description: Shared admin gate for the per-tenant Unleashed lifecycle endpoints.
      tags:
      - Integrations
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UnleashedConnectRequest'
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/UnleashedConnectRequest'
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/UnleashedConnectRequest'
        required: true
      security:
      - tokenAuth: []
      - cookieAuth: []
      responses:
        '201':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnleashedConnectResponse'
          description: ''
        '400':
          description: The supplied API credentials were rejected by Unleashed.
        '403':
          description: Caller is not a company admin.
        '502':
          description: Unleashed is unreachable, or the secret could not be stored.
      summary: Api v1 integrations unleashed connect create
      x-summary-source: derived
  /api/v1/integrations/unleashed/disconnect/:
    post:
      operationId: api_v1_integrations_unleashed_disconnect_create
      description: 'Disconnect Unleashed — delete the stored credentials and revoke the connection.


        Backs the single per-provider "Disconnect" CTA on the connector card (the

        generic ``DELETE /integrations/unleashed/`` returns 405 for resolver-backed

        connectors).'
      tags:
      - Integrations
      security:
      - tokenAuth: []
      - cookieAuth: []
      responses:
        '204':
          description: Connection revoked (idempotent).
        '403':
          description: Caller is not a company admin.
      summary: Api v1 integrations unleashed disconnect create
      x-summary-source: derived
  /api/v1/integrations/xero/supplier-spend/:
    get:
      operationId: api_v1_integrations_xero_supplier_spend_list
      description: Deterministic supplier-spend metrics over the buyer's ingested Xero bills.
      summary: Deterministic Xero supplier-spend breakdown for the current buyer
      parameters:
      - in: query
        name: window_months
        schema:
          type: integer
          minimum: 1
          maximum: 36
        description: Look-back window in months (default 12, clamped to 1..36).
      tags:
      - Integrations
      security:
      - tokenAuth: []
      - cookieAuth: []
      responses:
        '200':
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/XeroSupplierSpend'
          description: ''
components:
  schemas:
    ConnectorKindEnum:
      enum:
      - per_user
      - per_tenant
      type: string
      description: '* `per_user` - Per user

        * `per_tenant` - Per tenant'
    _CategorySpendRow:
      type: object
      properties:
        account_code:
          type: string
        spend:
          type: string
        supplier_count:
          type: integer
        supplier_names:
          type: array
          items:
            type: string
      required:
      - account_code
      - spend
      - supplier_count
      - supplier_names
    ConnectorConnectionStatusEnum:
      enum:
      - pending
      - connected
      - needs_reconnect
      - revoked
      - error
      type: string
      description: '* `pending` - Pending OAuth completion

        * `connected` - Connected

        * `needs_reconnect` - Needs reconnect

        * `revoked` - Revoked

        * `error` - Error'
    PlaidExchangeRequest:
      type: object
      description: Body for ``plaid/exchange/`` — the Link ``onSuccess`` payload.
      properties:
        public_token:
          type: string
        institution_id:
          type: string
          default: ''
        institution_name:
          type: string
          default: ''
      required:
      - public_token
    ConnectorVerifyResponse:
      type: object
      description: 'Coarse, closed-union result of the pre-flight token probe.


        Deliberately NOT the full ``ConnectorConnectionStatus`` set — the FE only

        needs to know whether the connection is usable, needs a reconnect, or is

        absent. Non-probed states (``pending`` / ``revoked`` / ``error``) are

        collapsed into one of these three before they leave the endpoint.'
      properties:
        status:
          $ref: '#/components/schemas/ConnectorVerifyResponseStatusEnum'
      required:
      - status
    PlaidItemStatusEnum:
      enum:
      - good
      - login_required
      - pending_expiration
      - revoked
      - error
      type: string
      description: '* `good` - Good

        * `login_required` - Login required

        * `pending_expiration` - Pending expiration

        * `revoked` - Revoked

        * `error` - Error'
    PlaidLinkTokenResponse:
      type: object
      properties:
        link_token:
          type: string
        expiration:
          type:
          - string
          - 'null'
          format: date-time
      required:
      - link_token
    UnleashedConnectRequest:
      type: object
      description: 'Body for ``unleashed/connect/`` — the API credentials the user pastes.


        Same endpoint backs both first connect and reconnect (re-pasting rotated

        credentials). Values are trimmed and validated with a live signed probe

        before anything is persisted.'
      properties:
        api_id:
          type: string
          maxLength: 255
        api_key:
          type: string
          maxLength: 1024
      required:
      - api_id
      - api_key
    ConnectorVerifyResponseStatusEnum:
      enum:
      - connected
      - needs_reconnect
      - not_connected
      type: string
      description: '* `connected` - connected

        * `needs_reconnect` - needs_reconnect

        * `not_connected` - not_connected'
    _SupplierSpendRow:
      type: object
      properties:
        contact_id:
          type: string
        name:
          type: string
        bill_count:
          type: integer
        spend:
          type: string
        spend_share:
          type: number
          format: double
        credit_note_total:
          type: string
        credit_note_ratio:
          type: number
          format: double
      required:
      - bill_count
      - contact_id
      - credit_note_ratio
      - credit_note_total
      - name
      - spend
      - spend_share
    AgentTokenResponse:
      type: object
      description: "Token-resolution outcome surfaced to the agent runtime.\n\n``status``:\n- ``ok`` — connection found and the vault returned a usable token.\n  ``access_token`` and ``expires_in`` are populated.\n- ``not_connected`` — no active connection exists for this\n  user/company. Token fields are absent.\n- ``needs_reconnect`` — there is an active row but the vault has no\n  usable token (revoked, expired, or never stored). Token fields are\n  absent."
      properties:
        status:
          $ref: '#/components/schemas/AgentTokenResponseStatusEnum'
        access_token:
          type: string
        expires_in:
          type: integer
      required:
      - status
    Connector:
      type: object
      description: Read-only view of a connector definition merged with its connection state.
      properties:
        provider:
          type: string
        display_name:
          type: string
        description:
          type: string
        icon_key:
          type: string
        kind:
          $ref: '#/components/schemas/ConnectorKindEnum'
        tenant_mode:
          $ref: '#/components/schemas/ConnectorTenantModeEnum'
        required_scopes:
          type: array
          items:
            type: string
        feature_flag:
          type: string
        status:
          oneOf:
          - $ref: '#/components/schemas/ConnectorConnectionStatusEnum'
          - $ref: '#/components/schemas/NullEnum'
        connected_at:
          type:
          - string
          - 'null'
          format: date-time
        last_verified_at:
          type:
          - string
          - 'null'
          format: date-time
        connected_by_name:
          type:
          - string
          - 'null'
        connected_organisation_name:
          type:
          - string
          - 'null'
          description: Human-readable name of the connected third-party organisation (e.g. the Xero organisation the token is bound to). Null when the connector exposes no such name or there is no active connection.
        is_connected_by_admin:
          type: boolean
        can_user_act:
          type: boolean
          description: True when the requesting user can connect/disconnect this connector.
      required:
      - can_user_act
      - connected_at
      - connected_by_name
      - description
      - display_name
      - feature_flag
      - icon_key
      - is_connected_by_admin
      - kind
      - last_verified_at
      - provider
      - required_scopes
      - status
      - tenant_mode
    UnleashedConnectResponse:
      type: object
      properties:
        status:
          type: string
        provider:
          type: string
      required:
      - provider
      - status
    InitiateResponse:
      type: object
      properties:
        authorization_url:
          type: string
          format: uri
        provider:
          type: string
      required:
      - authorization_url
      - provider
    PlaidItem:
      type: object
      description: Read-only view of one linked Plaid Item (bank connection).
      properties:
        uuid:
          type: string
          format: uuid
        item_id:
          type: string
        institution_id:
          type: string
        institution_name:
          type: string
        status:
          $ref: '#/components/schemas/PlaidItemStatusEnum'
        last_synced_at:
          type:
          - string
          - 'null'
          format: date-time
      required:
      - institution_id
      - institution_name
      - item_id
      - last_synced_at
      - status
      - uuid
    NullEnum:
      enum:
      - null
    PlaidExchangeResponse:
      type: object
      properties:
        status:
          $ref: '#/components/schemas/PlaidExchangeResponseStatusEnum'
        item:
          $ref: '#/components/schemas/PlaidItem'
      required:
      - item
      - status
    AgentTokenResponseStatusEnum:
      enum:
      - ok
      - not_connected
      - needs_reconnect
      type: string
      description: '* `ok` - ok

        * `not_connected` - not_connected

        * `needs_reconnect` - needs_reconnect'
    AgentTokenRequest:
      type: object
      description: 'JSON body the agent runtime POSTs to ``agent-token``.


        The runtime passes the active turn''s ``company_uuid`` so the lookup

        targets the right tenant for multi-company users; absent means fall

        back to ``user.get_company()`` (the single-company case Opply assumes

        today).'
      properties:
        company_uuid:
          type: string
          format: uuid
    PlaidLinkTokenRequest:
      type: object
      description: 'Body for ``plaid/link-token/``.


        ``item_uuid`` puts Link into **update mode** to re-auth an existing Item

        (e.g. after ITEM_LOGIN_REQUIRED); omit it to link a new bank.'
      properties:
        item_uuid:
          type: string
          format: uuid
    XeroSupplierSpend:
      type: object
      description: Response shape for the supplier-spend aggregation (schema only).
      properties:
        base_currency:
          type: string
        window_months:
          type: integer
        total_spend:
          type: string
        bill_count:
          type: integer
        supplier_count:
          type: integer
        skipped_bills:
          type: integer
        suppliers:
          type: array
          items:
            $ref: '#/components/schemas/_SupplierSpendRow'
        categories:
          type: array
          items:
            $ref: '#/components/schemas/_CategorySpendRow'
      required:
      - base_currency
      - bill_count
      - categories
      - skipped_bills
      - supplier_count
      - suppliers
      - total_spend
      - window_months
    PlaidExchangeResponseStatusEnum:
      enum:
      - ok
      type: string
      description: '* `ok` - ok'
    AgentTokenErrorResponse:
      type: object
      description: Stable client message surfaced on upstream identity-broker failures.
      properties:
        error:
          type: string
      required:
      - error
    ConnectorTenantModeEnum:
      enum:
      - none
      - shared_token
      - admin_consent
      type: string
      description: '* `none` - None (per-user connector)

        * `shared_token` - Shared admin token

        * `admin_consent` - Provider admin-consent'
  securitySchemes:
    cookieAuth:
      type: apiKey
      in: cookie
      name: sessionid
    tokenAuth:
      type: apiKey
      in: header
      name: Authorization
      description: Token-based authentication with required prefix "Token"