{"openapi":"3.0.3","info":{"title":"Vector Trading Public API","version":"1.0.0","description":"Public REST API for external integrations built on Vector Trading account API keys. The current version includes bots bundles listing, user search, checkout sessions, and access grants management. Requests from IP addresses outside the key's optional CIDR masks are rejected."},"servers":[{"url":"/api/rest"}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"API Key","description":"Use the API key created in the Profile > API Keys tab. Optional IP masks configured on the key are enforced automatically."}},"schemas":{"ApiKeyPermissionLevel":{"type":"string","enum":["none","read","write"]},"ReadOnlyApiKeyPermissionLevel":{"type":"string","enum":["none","read"]},"AccountApiKeyPermissions":{"type":"object","required":["bundles","grants","users"],"properties":{"bundles":{"$ref":"#/components/schemas/ReadOnlyApiKeyPermissionLevel"},"grants":{"$ref":"#/components/schemas/ApiKeyPermissionLevel"},"users":{"$ref":"#/components/schemas/ReadOnlyApiKeyPermissionLevel"}}},"PublicUser":{"type":"object","required":["userId","displayName"],"properties":{"userId":{"type":"string","example":"d7d8308a1b9542f5b7fbab89354b55e1"},"displayName":{"type":"string","example":"TraderAlex"},"avatar":{"type":"string","nullable":true,"example":"https://example.com/avatar.png"}}},"OwnedTradingBundleSummary":{"type":"object","required":["_id","name","marketType","quote","access","status","grantedUsersCount"],"properties":{"_id":{"type":"string","example":"2be97361321c46f2bc7300a04fd6d133"},"name":{"type":"string","example":"BTC Momentum"},"marketType":{"type":"string","example":"swap"},"quote":{"type":"string","example":"USDT"},"access":{"type":"string","enum":["private","public","restricted"]},"status":{"type":"string","enum":["active","partially_active","paused","partially_paused","stopped","archived"]},"grantedUsersCount":{"type":"integer","example":3},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"TradingBundleAccessGrantType":{"type":"string","enum":["trial","owner_grant","paid_external","referral_reward","gift"]},"TradingBundleGrantedUser":{"type":"object","required":["user"],"properties":{"user":{"$ref":"#/components/schemas/PublicUser"},"grantId":{"type":"string","example":"dbd3d7f79cf846dd91f3db12f3ebbb06"},"grantType":{"$ref":"#/components/schemas/TradingBundleAccessGrantType"},"startsAt":{"type":"string","format":"date-time"},"endsAt":{"type":"string","format":"date-time"},"accessEndsAt":{"type":"string","format":"date-time"},"accessIsPermanent":{"type":"boolean","example":false}}},"TradingBundleAccessGrantSummary":{"type":"object","required":["id","grantType"],"properties":{"id":{"type":"string","example":"dbd3d7f79cf846dd91f3db12f3ebbb06"},"grantType":{"$ref":"#/components/schemas/TradingBundleAccessGrantType"},"userId":{"type":"string","example":"7bb8d364a6984fbab05a06a6c7f85db2","description":"Owner-scoped user alias. Omitted when the Account is missing or permanently deleted."},"sourceId":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$","maxLength":256,"example":"stripe:invoice:in_123"},"startsAt":{"type":"string","format":"date-time"},"endsAt":{"type":"string","format":"date-time"},"createdAt":{"type":"string","format":"date-time"}}},"UsersSearchResponse":{"type":"object","required":["users","query","limit"],"properties":{"users":{"type":"array","items":{"$ref":"#/components/schemas/PublicUser"}},"query":{"type":"string","example":"alex"},"limit":{"type":"integer","minimum":1,"maximum":50,"example":10},"nextCursor":{"type":"string","description":"Opaque cursor to pass unchanged on the next request."}}},"BundlesResponse":{"type":"object","required":["bundles","limit"],"properties":{"bundles":{"type":"array","items":{"$ref":"#/components/schemas/OwnedTradingBundleSummary"}},"limit":{"type":"integer","minimum":1,"maximum":100,"example":50},"nextCursor":{"type":"string","description":"Opaque cursor to pass unchanged on the next request."}}},"BundleUsersResponse":{"type":"object","required":["users","limit"],"properties":{"users":{"type":"array","items":{"$ref":"#/components/schemas/TradingBundleGrantedUser"}},"limit":{"type":"integer","minimum":1,"maximum":100,"example":50},"nextCursor":{"type":"string","pattern":"^(?:grant|scan):[a-f0-9]{32}$","description":"Opaque owner-scoped cursor to pass unchanged on the next page request."}}},"BundleGrantsResponse":{"type":"object","required":["grants","limit"],"properties":{"grants":{"type":"array","items":{"$ref":"#/components/schemas/TradingBundleAccessGrantSummary"}},"limit":{"type":"integer","minimum":1,"maximum":100,"example":50},"nextCursor":{"type":"string","description":"Opaque cursor to pass unchanged on the next request."}}},"CreateGrantRequest":{"type":"object","required":["userId","grantType"],"description":"Creates a current or future bundle access grant. Use endsAt for exact paid period boundaries; omit it only for permanent access. paid_external grants require sourceId.","oneOf":[{"required":["grantType","sourceId"],"properties":{"grantType":{"type":"string","enum":["paid_external"]}}},{"properties":{"grantType":{"type":"string","enum":["owner_grant","referral_reward","gift"]}}}],"properties":{"userId":{"type":"string","example":"7bb8d364a6984fbab05a06a6c7f85db2"},"grantType":{"type":"string","enum":["owner_grant","paid_external","referral_reward","gift"]},"endsAt":{"type":"string","format":"date-time","example":"2026-07-19T12:00:00.000Z"},"sourceId":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$","maxLength":256,"example":"stripe:invoice:in_123","description":"Visible ASCII dedupe key for external flows. Required for paid_external grants. Reuse for the same bundle, user, and grant type is rejected. Allowed characters: letters, digits, dot, underscore, colon, and dash."}}},"GrantMutationResponse":{"type":"object","required":["grant"],"properties":{"grant":{"$ref":"#/components/schemas/TradingBundleAccessGrantSummary"}}},"CheckoutDetails":{"type":"object","required":["checkoutId","bundleId","userId","displayName"],"description":"Short-lived checkout details for an owner checkout. price, currency, and discount are current bundle display terms; trialPeriodDays is intentionally not returned.","properties":{"checkoutId":{"type":"string","example":"5668c2dbeec242de9c78d21aa524d355"},"bundleId":{"type":"string","example":"2be97361321c46f2bc7300a04fd6d133"},"userId":{"type":"string","example":"7bb8d364a6984fbab05a06a6c7f85db2"},"displayName":{"type":"string","example":"TraderAlex"},"price":{"type":"number","example":49},"currency":{"type":"string","example":"USDT"},"discount":{"type":"object","properties":{"type":{"type":"string","enum":["fixed","percent"]},"value":{"type":"number","example":10}}}}},"ErrorResponse":{"type":"object","required":["errorCode","message"],"properties":{"errorCode":{"type":"string","example":"errors.vector.access_denied"},"message":{"type":"string","example":"You are not authorized to perform this operation"},"metadata":{"type":"object","additionalProperties":{"type":"string"}},"requestId":{"type":"string"}}}}},"security":[{"bearerAuth":[]}],"paths":{"/v1/bundles":{"get":{"summary":"List your bots bundles","description":"Returns non-archived bots bundles owned by the API key owner. Source reads are bounded, so an archived-heavy page can be partial or empty while nextCursor is present. Continue until nextCursor is absent.","parameters":[{"in":"query","name":"limit","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"in":"query","name":"cursor","required":false,"schema":{"type":"string"},"description":"Opaque cursor from the previous response."}],"responses":{"200":{"description":"Owned bots bundles","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BundlesResponse"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Missing API key scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/users":{"get":{"summary":"Search users by name","parameters":[{"in":"query","name":"displayName","required":true,"schema":{"type":"string","minLength":2,"maxLength":30},"description":"Display name prefix searched across active users except the API key owner."},{"in":"query","name":"limit","required":false,"schema":{"type":"integer","minimum":1,"maximum":50,"default":10}},{"in":"query","name":"cursor","required":false,"schema":{"type":"string"},"description":"Opaque cursor from the previous response. Keep displayName unchanged between pages."}],"responses":{"200":{"description":"Search results","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsersSearchResponse"}}}}}}},"/v1/checkout/{checkoutId}":{"get":{"summary":"Get checkout session","description":"Returns a short-lived checkout session for a bundle owner API key, including the current bundle display payment terms.","parameters":[{"in":"path","name":"checkoutId","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Checkout details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutDetails"}}}}}}},"/v1/bundles/{bundleId}/users":{"get":{"summary":"List users with bundle access","parameters":[{"in":"path","name":"bundleId","required":true,"schema":{"type":"string"}},{"in":"query","name":"limit","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"in":"query","name":"cursor","required":false,"schema":{"type":"string","pattern":"^(?:grant|scan):[a-f0-9]{32}$"},"description":"Opaque owner-scoped cursor from the previous response."}],"responses":{"200":{"description":"Users with active or scheduled grants","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BundleUsersResponse"}}}}}}},"/v1/bundles/{bundleId}/grants":{"get":{"summary":"List bundle access grants","description":"Returns non-revoked grants for the selected bundle, including expired grants. Missing or permanently deleted Accounts remain in the result without userId. Source reads are bounded, so a filtered page can be partial or empty while nextCursor is present. Keep filters and sorting unchanged and continue until nextCursor is absent.","parameters":[{"in":"path","name":"bundleId","required":true,"schema":{"type":"string"}},{"in":"query","name":"userId","required":false,"schema":{"type":"string"},"description":"Viewer-scoped public user alias returned by the public users API."},{"in":"query","name":"grantType","required":false,"schema":{"$ref":"#/components/schemas/TradingBundleAccessGrantType"}},{"in":"query","name":"sourceId","required":false,"schema":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$","maxLength":256}},{"in":"query","name":"startsAfter","required":false,"schema":{"type":"string","format":"date-time"}},{"in":"query","name":"startsBefore","required":false,"schema":{"type":"string","format":"date-time"}},{"in":"query","name":"endsAfter","required":false,"schema":{"type":"string","format":"date-time"}},{"in":"query","name":"endsBefore","required":false,"schema":{"type":"string","format":"date-time"}},{"in":"query","name":"createdAfter","required":false,"schema":{"type":"string","format":"date-time"}},{"in":"query","name":"createdBefore","required":false,"schema":{"type":"string","format":"date-time"}},{"in":"query","name":"limit","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"in":"query","name":"cursor","required":false,"schema":{"type":"string"},"description":"Opaque cursor from the previous response. Keep filters and sorting unchanged between pages."},{"in":"query","name":"sort","required":false,"schema":{"type":"string","enum":["createdAt","startsAt","endsAt"],"default":"createdAt"}},{"in":"query","name":"dir","required":false,"schema":{"type":"string","enum":["asc","desc"],"default":"desc"}}],"responses":{"200":{"description":"Non-revoked grants matching the search filters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BundleGrantsResponse"}}}}}},"post":{"summary":"Create bundle access grant","description":"Creates a grant until an exact access end. For recurring external payments, call this endpoint after every successfully paid invoice with a new sourceId and the invoice period end. Reusing an existing sourceId for the same bundle, user, and grant type returns a conflict error.","parameters":[{"in":"path","name":"bundleId","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateGrantRequest"}}}},"responses":{"200":{"description":"Created grant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrantMutationResponse"}}}},"409":{"description":"sourceId already exists for the same bundle, user, and grant type","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"JSON request body is too large","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"415":{"description":"Request body must use application/json","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/bundles/{bundleId}/grants/{grantId}":{"delete":{"summary":"Revoke bundle access grant","description":"Revokes the selected grant and all current or future grants for the same user in this bundle that start after it.","parameters":[{"in":"path","name":"bundleId","required":true,"schema":{"type":"string"}},{"in":"path","name":"grantId","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Revoked grant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GrantMutationResponse"}}}}}}}}}