This endpoint connects one of your customer's own content sources: their mailbox, their Drive, their YouTube channel, another website. It is driven from your product, over the API. There is no Context Link screen for your customer to visit.
Sources are addressed by a uid you choose, not by type, because one customer can hold several of the same kind (two mailboxes, three channels). The uid is your own word for the source. The write verb is a PUT upsert, so a retried request is always safe.
Request fields
uidstring404.connection_uidstringa-z, 0-9, hyphen, underscore or dot, 1 to 255 characters, unique per customer. It is the address for every later call on this connection.connection_typestringYou choose which types your organisation can offer, on the Connection types screen. A type you have not turned on is a 403 connection_type_not_activated. An OAuth type other than Google Drive turned on without your own OAuth keys is a 409 partner_credentials_required, on this call and on re-authorising, until you add them there.
Setup fields by type
Every type takes connection_type plus the fields below.
connection_type |
Required fields | Authorisation |
|---|---|---|
site |
site_domain (e.g. https://acme.com) |
None. Connects directly, one website per customer. |
email |
email_address, imap_password, imap_server; optional imap_port (defaults to 993) |
None. Credentials are checked on the first sync. |
youtube |
handle (e.g. @acmecorp) |
None. The handle is resolved to a channel before the connection is created. |
monday |
api_token (pasted by the customer); optional workspace_id |
None. The token is resolved to a workspace before the connection is created. If it can see more than one workspace, send workspace_id. |
webmention |
brand_name, web_domain |
None. Web Mentions of the brand across search and news. |
google_doc |
return_url |
Redirect. Google consent screen. Indexes the whole of the customer's My Drive. There is no folder to choose, because you never see inside it. |
one_drive |
return_url |
Redirect. Microsoft consent screen. |
notion |
return_url |
Redirect. Notion consent screen. |
basecamp |
return_url, project_url (e.g. https://3.basecamp.com/1234567/projects/9876543) |
Redirect. Basecamp consent screen. One project per connection. The project has to belong to the account the customer authorises, or the connection comes back auth_failed. |
A missing or invalid field is a 422 carrying a per-field errors object, so you can put the message next to the input the customer filled in:
Mail connections default to the INBOX folder. Everything a customer pushes through the Custom Connections API is unaffected by any of this.
Response fields
Every successful call returns a single connection object.
| Field | Type | Description |
|---|---|---|
connection |
object | The connection that was created or replayed. |
connection.id |
integer | Context Link's own id for the connection. You address it by the uid you chose, so you never need to store this. |
connection.uid |
string | The uid you chose for this source, echoed back. |
connection.connection_type |
string | The type, in the same form you sent it (google_doc, youtube). |
connection.name |
string | The type's own display name, such as GoogleDoc or Youtube. Never something you supply. |
connection.status |
string | Where the connection has got to: redirect_required, pending or connected. This is the value to branch on, and it is covered below. |
connection.auth_status |
string | pending_auth, authorised, auth_failed or disconnected. A type that needs no consent screen is authorised from the moment it is created. |
connection.sync_status |
string | unsynced, syncing, synced or error_syncing. A connection that has not yet attempted a sync is unsynced. |
connection.redirect_url |
string | The consent-screen URL to send the customer to. Present only when status is redirect_required. On any other status the key is left out of the response rather than returned as null. |
connection.expires_at |
timestamp | When that redirect_url stops working, 15 minutes after it was minted. Present only alongside redirect_url, on the same terms. |
Two mailboxes on one customer are told apart by their uid. That is your own label, and the only one your customer will recognise.
Branch on status, not on redirect_url
Every response carries one status discriminator with three values. Branching on "is redirect_url present" mishandles the third.
status |
What it means | What to do |
|---|---|---|
redirect_required |
The type needs the customer's consent at the provider. redirect_url and expires_at are present. |
Send the customer's browser to redirect_url before expires_at. |
pending |
Connected and authorised, first sync still running. | Nothing. Wait for the connection.authorised webhook or poll. |
connected |
Authorised and synced at least once. | Query it. |
201 on create, 200 on a replay of the same uid. A replay ignores body drift, exactly like POST /api_accounts. There is one exception. Changing the connection_type of a live uid is a 409 connection_type_immutable, because the content already indexed under it would be meaningless as another type. Delete and recreate instead.
A replay of a connection still in pending_auth re-mints a fresh redirect_url, because the usual reason you are retrying is that the customer abandoned the first consent screen.
The redirect flow
For the four OAuth types the customer has to say yes at the provider, so the flow is a browser round trip that starts and ends in your product:
- You
PUTthe connection with areturn_url. You get backstatus: "redirect_required"and aredirect_url. - You send the customer's browser to
redirect_url. They consent at Google / Microsoft / Notion / Basecamp, under your brand and using your own OAuth client. - The provider returns them to Context Link, which exchanges the code and redirects the browser to your
return_urlwith?connection_status=connectedor?connection_status=failedappended. Your existing query string is preserved. - The authoritative record is the
connection.authorisedorconnection.auth_failedwebhook, which fires whatever the browser does.
return_url is required for these types, and it must be a publicly routable http(s) URL. The same host rules apply as for a webhook target. A private or loopback address is a 422 at request time.
The redirect_url is signed and expires 15 minutes after it is minted. An expired or tampered link shows the customer a plain "this link has expired, start again" page rather than bouncing them anywhere. At that point nothing proves where they came from.
Provider verification is yours to obtain. The consent screen shows your brand because it is your OAuth client. That also means the provider's unverified-app caps and review processes apply to you: Google restricts Gmail and Drive scopes behind an annual third-party security assessment, and an unverified client is limited to a small number of users. Start that process before you launch the type to your fleet.
Re-authorising a dead connection
Refresh tokens die: the customer changes their password, revokes access at the provider, or the grant simply expires. That sets auth_status: "auth_failed" and fires connection.auth_failed. Nothing is deleted. The indexed content stays, because the usual cause is transient. Re-authorising should not mean re-crawling from nothing.
You get a fresh redirect_url to send the customer to. Pass a return_url to override the one stored at create time. On a type that has no authorisation to renew this is a 422 connection_type_not_reauthorisable.
Limits
| Limit | Value |
|---|---|
| Connections per customer, all types | 10, and the memory connection is exempt |
| Connections of one type per customer | 5, counting toward the 10 |
| Websites per customer | 1, counting toward the 10 |
| Custom connections per customer | 6, counting toward the 10 |
| Posts per sync per connection | 500 by default; lower where a sync is expensive: 30 for YouTube, 50 for Web Mentions |
| Write rate limit | 5 per 10s per token and per organisation |
10 is the binding ceiling. The per-type numbers are maxima within it, not slices that add up to it. A customer can hold 6 custom connections or 5 mailboxes, but not both, because the total still has to come in at 10.
A customer's memory connection is the one exception: it never counts toward the 10, and it has no cap of its own to worry about.