Skip to main content

Partner API

The Partner API gives partner users a company-scoped way to work with the Journeybee data shared with them. The partner surface is separate from the admin API and follows the same room, partnership, and sharing settings as the partner portal.

Base URL

Authentication

Partner connections use either OAuth or a partner API key. Send the credential in the Authorization header:
OAuth (authorization-code + PKCE) is how interactive MCP clients — Claude, ChatGPT, Cursor, and similar — connect to your partnership data. Discovery and client registration happen automatically via the MCP server, so there’s nothing to configure by hand; see Partner MCP for the connect flow. For custom REST integrations, use a partner API key instead. Partner API keys are issued in the partner portal and are shown only once. Store them securely and send them as bearer credentials.

Getting started

Call GET /v1/partner/me with your credential to find out who you are and which partnerships you can act on:
The response includes your user identity, your read/write permissions, and the partnerships available to you — each with a uuid, name, partner_type, and enabled. That uuid is the {partnershipId} you send on every other request (see Company and partnership scope below).

Examples

List deals for a partnership:
Create a lead:

API keys

Partner API keys are created and revoked on the portal’s API & MCP page. When you create one you choose read and/or write access — the same scopes an OAuth connection carries — and the full key is shown exactly once, so store it securely. A key is scoped to your partner user and the company whose portal you created it on (not to a single partnership), and starts with the jb_partner_ prefix. Each partner user may have up to 10 active keys per company — revoke ones you no longer need before creating more.

Scopes and permissions

Connections can have read and write access. Most partner operations are read-only; write endpoints require the connection to have write access. Grant only the permissions an integration needs.

Company and partnership scope

Credentials are scoped to a partner user and company. A partner can have connections for multiple companies, and each company must be authorized separately. The partnership is selected for each API request:
The API validates that the authenticated partner is an active member of the requested partnership at the credential’s company. A credential never grants access to partnerships at another company.

IDs are UUIDs

Every id in a partner API request or response is a UUID — partnership, lead, deal, tag, user, and contact ids are all uuid strings. IDs you receive back from a request round-trip directly into filters and update calls; the API never exposes numeric ids as resource identifiers. A few numeric fields do appear as display-only metadata (logo/asset ids, custom-field option ids inside options) — these are never accepted as identifiers in a request.

Responses & errors

List endpoints return a paginated envelope:
Errors follow a single envelope:
code is one of unauthorized, forbidden, module_disabled, not_found, validation_error, conflict, rate_limit_exceeded, or internal_error. module_disabled means the company hasn’t enabled the Partner API for its portal. Requests are rate-limited to 500 requests per minute per partner user — each partner user has their own budget, separate from other users at the same company. Exceeding it returns a 429 Too Many Requests response with a Retry-After header.

Field requirements

Custom fields on leads and deals can be marked required, or controlled by a custom field rule (auto-populating, hiding, or locking another field). Before creating or updating a lead or deal, call the matching requirements endpoint to learn the constraints your submission must satisfy:
The response lists the custom fields available to this partnership — with each field’s required flag — and the active rules that apply to them:
required applies only while a field is visible — a required field hidden by one of the listed rules must not be supplied (submitting it returns hidden_by_rule); it becomes required once its rule condition makes it visible. In the example above, “Renewal Terms” is required, but only while the “Show renewal terms for large Enterprise deals” rule keeps it visible.
A rule’s primary condition is source_field with source_values. A condition matches when the field’s value matches any of its values, and a number field can use a threshold such as { "operator": ">", "value": 100 }. A rule can also carry extra conditions, each with its own field and values, and a condition_operator:
  • For show_field and set_value, the primary condition and every extra condition are combined with condition_operator: and needs all of them to match, or needs any one. The example rule shows “Renewal Terms” only for an Enterprise deal with more than 100 rooms.
  • For value_mapping, the extra conditions are a gate. They are combined among themselves with condition_operator, and the first matching value_mappings row on source_field applies only while the gate passes. With no extra conditions, the gate always passes.
conditions is an empty array when a rule has only its primary condition, so a client that evaluates rules itself must check every entry, not just source_field. Deal field requirements follow the deal’s owning partnership; when creating a deal from another partnership’s lead (a distributor roll-up), the lead’s own partnership type governs instead. Only fields and rules you’re actually allowed to read/write on this surface are included. A rule is left out entirely when its target field or any of its condition fields (the source field or any field in conditions) isn’t visible to a partner, since it can never be satisfied. Both endpoints require the same room access (leads/deals) as the corresponding list/create endpoints. If a create or update call violates one of these constraints, it returns 400 validation_error with a details array — one entry per rejected field, each with the field’s id (uuid), a machine-readable reason (e.g. missing_required, hidden_by_rule, locked_by_rule), and a human-readable message. See Custom field validation for the full reason code reference. The standard deal name follows the same rule. When the vendor hides the deal name, POST /v1/partner/{partnershipId}/deals rejects a non-empty deal_label and PATCH /v1/partner/{partnershipId}/deals/{dealId} rejects a non-empty fields.label. Either returns 400 validation_error with a details entry whose field is deal_label (create) or fields.label (update) and whose reason is hidden_by_rule; a rejected update applies none of its other fields. Omit the label instead: a created deal is then named after the lead’s company name, and an existing deal keeps its current name.

Lead status forms

A vendor can attach a form to two lead statuses: the Accepted form (status converted) and the Rejected form (status rejected). The leads requirements endpoint returns both under status_forms (the deals endpoint does not):
  • mode is off (no form), optional (the fields and reason may be sent), or required (every required field and a required reason must be sent). A field’s required flag only binds while the mode is required.
  • fields lists only the form’s fields the vendor shares with partners.
  • reason is the form’s built-in reason: accepted_reason for the Accepted form, rejected_reason for the Rejected form. It is null when the vendor hides that reason from partners. required is true only when the mode is required and the vendor requires the reason.
Send the form in the same updatePartnerLead request that changes the status: answers in custom_field_values by field uuid, and the reason in accepted_reason or rejected_reason:
The updated lead comes back with the reason and the current form’s answers:
(Other lead fields are omitted above.) rejected_reason and accepted_reason are set only while the lead is in their status and the vendor shows them to partners; otherwise they are null. status_form_fields holds the shared answers of the current status’s form, and is empty while the lead is in any other status. The request is refused, and nothing in it is saved, when:
  • the form is required and a required field or a required reason is missing. This returns 400 validation_error with a missing_required detail for each one (field is the field’s uuid, or accepted_reason / rejected_reason).
  • a form answer or a reason is sent without moving the lead into that form’s status, or a reason is sent while the vendor hides it. This returns 400 validation_error with a not_accessible detail. Sending back the reason value you can already read for the lead (null when hidden or not in that status) is not a change and is accepted.
  • the partnership has lead status locked and the request changes the status or a reason. See Partnership locks.
Answers sent for the form of the status the lead is leaving are ignored, not refused, because leaving that status clears the form’s shared answers. rejected_reason and accepted_reason are limited to 2000 characters. When the lead later leaves Accepted or Rejected, that form’s shared answers and its reason (when you can see it) are cleared.

Visibility

Partner API responses follow the same visibility rules as the partner portal. Room settings, partnership settings, sharing settings, campaign sharing, and ownership rules can affect which records are visible or writable. The leads and deals endpoints specifically require the company to expose the matching room (leads or deals) to that partnership in the partner portal — the same category, tier, stage, contact-tag, and visibility rules that gate the room there. If a partnership has no accessible room of that type, its leads or deals endpoints return 403 forbidden with "The Leads area is not available in your partner portal. Ask the vendor to enable it." or "The Deals area is not available in your partner portal. Ask the vendor to enable it.". Ask the vendor to enable the relevant area in the partner portal. Deal creation can additionally be disabled independently of read/write access, via the deals room’s settings — in that case POST /v1/partner/{partnershipId}/deals returns 403 forbidden with "Deal creation is disabled in your partner portal. Ask the vendor to enable deal creation.", while reading and updating existing deals is unaffected.

Co-sell deals

When the vendor has the Co-selling module enabled, it can add your partnership to another partner’s deal as a supporting partner. The deal then shows up in GET /v1/partner/{partnershipId}/deals and GET /v1/partner/{partnershipId}/deals/{dealId}, and you can update it with PATCH, for as long as your partnership is an active partner of the deal’s partner type and the module stays on. If the vendor removes you from the deal or turns the module off, the deal is no longer visible to you. Every deal response carries partners: every partner on the deal, with the primary partner (is_primary: true, the deal’s own partner) first, then each supporting partner, oldest first, with its role (a label the vendor defines, or null). The primary partner can carry a role too, such as “Sourced” or “Sourced + Co-sold”: the one the vendor picked for the deal, or the vendor’s first primary role when none was picked. It is null when the vendor has no primary roles for the deal’s partner type. Partners can see these roles but not change them. As a supporting partner you work the deal without its money and its lead:
  • Money fields come back empty: total_commission_value and total_commission_occurrences are null, commission and spiff values are not returned, and has_quote is false.
  • The deal’s lead is not reachable. The leads endpoints do not return it, and you cannot create deals from it.
  • Archiving the deal, creating deals on its lead, and the deal’s payments stay with the primary partner.
  • The primary partner’s distributor link (partnership.partnership_distributors_id, distributor_name and the distributor logos) comes back null.
  • The primary partner’s deal-stage lock binds you too. When it is on, lock_deal_stage is true in the deal list, and changing fields.stage_id returns 403 forbidden with the locked_by_partnership reason.
On a co-sell deal, assigned_contacts holds the contacts of every partner on the deal, so you see the primary’s and other supporting partners’ contacts beside your own.

Reference

Every partner endpoint — full request and response schemas — is documented in the Partner API Reference section of these docs, generated from the same spec this guide describes.