Invite a member
- Permissions
members:read,members:invite- Safe retries
Idempotency-Key- Also available as
fieldtask_create_member_invitation
Invites one person, by email address, to join your organization as an ordinary member.
The request creates a pending invitation, not a member. After it's saved, FieldTask emails the person a link as separate follow-up work. They become a member only when they open that link, sign in with the invited address and accept. Until then they don't appear in List members and have no member ID.
This endpoint only creates. It never resends, replaces or changes an existing invitation, and it can't invite admins. To retry safely after a lost response, send the same body with the same Idempotency-Key. See Idempotency and safe retries.
This page is generated from contract 2026-11-03-review.2, a review export that hasn't been released. Invitation creation works only where FieldTask has enabled it.
Before you start
You need:
- API access for your organization. An owner or admin requests it in Settings → API connections. Until FieldTask enables it, every request returns
403 api_access_not_enabled. - A REST credential with the permissions below: an API token created in Settings → API connections, or an access token from an OAuth client-credentials connection. Tokens from an MCP sign-in don't work with the REST API.
- An eligible invitation sponsor. The connection invites on behalf of an organization owner or admin, who must still hold that role. See Invitation authority.
- A plan that allows inviting members. Solo plans can't invite members, and some plans limit how many invitations an organization can create. Either case returns
422 validation_failed.
Permissions
| Permission | Dependency in the permission map |
|---|---|
members:read | No dependencies |
members:invite | Depends on members:read |
No inputs need additional permissions.
To check the outcome later with GET /public/v1/operations/{id}, the connection needs the same two permissions.
Adding a permission to a connection doesn't happen automatically. An owner or admin edits it in Settings → API connections. API tokens keep the same token string when their permissions change. OAuth integrations must also request the new scope when they get an access token.
Request
Responses
- 201
- 400
- 401
- 403
- 404
- 405
- 409
- 410
- 413
- 415
- 422
- 429
- 503
Created
Response Headers
Responses must not be cached.
The operation key used for this create, including a server-generated key when omitted.
Server-generated request correlation ID.
Bad Request
Response Headers
Responses must not be cached.
Server-generated request correlation ID.
Unauthorized
Response Headers
Responses must not be cached.
Server-generated request correlation ID.
Forbidden
Response Headers
Responses must not be cached.
Server-generated request correlation ID.
Not Found
Response Headers
Responses must not be cached.
Server-generated request correlation ID.
Method Not Allowed
Response Headers
Responses must not be cached.
Server-generated request correlation ID.
Conflict
Response Headers
Responses must not be cached.
Seconds before retry; present on 429 and an in-progress operation.
Server-generated request correlation ID.
Gone
Response Headers
Responses must not be cached.
Server-generated request correlation ID.
Request Entity Too Large
Response Headers
Responses must not be cached.
Server-generated request correlation ID.
Unsupported Media Type
Response Headers
Responses must not be cached.
Server-generated request correlation ID.
Unprocessable Entity
Response Headers
Responses must not be cached.
Server-generated request correlation ID.
Too Many Requests
Response Headers
Responses must not be cached.
Seconds before retry; present on 429 and an in-progress operation.
Server-generated request correlation ID.
Service Unavailable
Response Headers
Responses must not be cached.
Server-generated request correlation ID.
Invitation authority
Invitations are always sent on behalf of a person who is allowed to invite: the organization's owner or an active admin. The API calls this person the connection's invitation sponsor.
- For a REST connection, the sponsor is the owner or admin who created the connection with
members:invite, or who most recently saved its permissions in Settings → API connections. - The sponsor must stay eligible: their FieldTask account must be active and they must still be the owner or an admin. Their role is checked on every invitation request, not just when the connection was set up.
- If the sponsor is no longer eligible, every invitation request on the connection (create, list and get) returns
403 operation_forbidden. - To fix it, a current owner or admin edits the connection's permissions in Settings → API connections and saves. The person who saves becomes the sponsor.
Each invitation stays tied to the sponsor who was in place when it was created. It can be emailed and accepted only while that person is still the connection's sponsor and still eligible. If the sponsor changes, or loses their role, invitations that are still pending can no longer be accepted; invite those people again once the old invitations have expired or been cancelled.
Through MCP, the sponsor is the owner or admin who approved the AI client's connection. See fieldtask_create_member_invitation.
Field guide
The request body is a single JSON object. Only email is required.
| Field | Required | What to send |
|---|---|---|
email | Yes | One email address, up to 256 characters, with no spaces at either end. It's stored exactly as sent. |
role | No | Only member is accepted. Defaults to member. Any other value, such as admin, is rejected. |
Anything else in the body is rejected with 422 validation_failed, including fields such as name, projects or organization_id. The organization always comes from your credential.
About email:
- Comparisons are case-insensitive.
Jordan.Example@example.comandjordan.example@example.comare the same recipient. - If the address already has a pending invitation in your organization, the request is rejected with
422 validation_failed. This includes invitations created in the FieldTask app. A new key never resends or replaces a pending invitation. - FieldTask doesn't check whether the address belongs to an existing FieldTask account or member, and the response never reveals it.
- The response doesn't say which rule failed. Validation errors never echo the submitted address.
Body and header rules
- Send
Content-Type: application/jsonwith an uncompressed body. AContent-Encodingheader is rejected with415. - The body can be at most 256 KiB and nest at most 10 levels deep.
- Duplicate property names,
NaNandInfinityare rejected with400 invalid_json. - The URL must have no query string and no trailing slash.
What happens after you create an invitation
| Step | What happens | What you see |
|---|---|---|
| 1. Created | The invitation is saved with status pending. It expires seven days after created_at. | 201 with the invitation. expires_at is set. |
| 2. Emailed | FieldTask emails the recipient a link, as follow-up work after the request. | The operation's integration status. See Email delivery. |
| 3. Accepted | The recipient signs in with the invited address and accepts. They join as an ordinary member. | The invitation's status becomes accepted, and the person appears in List members. |
| Or: expired | Nobody accepted before expires_at. The link stops working. | status becomes expired. |
A 201 response means the invitation exists. It doesn't mean the email was sent or that the person has joined. To follow the invitation, read it with Get a member invitation using data.id or the Location header.
The acceptance link is never returned by the API. It's only in the email.
Email delivery
Sending the email is follow-up work reported on the operation, not on the invitation. Read the Operation-Location URL from the 201 response with the same credential:
curl "$FIELDTASK_API_ORIGIN/public/v1/operations/OPERATION_ID" \
--header "Authorization: Bearer $FIELDTASK_API_TOKEN"
data.local_status is always committed: the invitation exists. Right after the create, data.integration.status is pending with one target for the email. That target's status and error_code tell you what happened:
Target status | error_code | Meaning |
|---|---|---|
pending | null | The email is queued, being sent, or waiting to be retried after a temporary mail-server problem. FieldTask retries those automatically. |
succeeded | null | The mail server accepted the message. This isn't proof that it reached the inbox. |
failed | invalid_upstream_data | The mail server refused the message, for example because the address doesn't exist. |
failed | provider_unavailable | The mail server couldn't be reached after repeated attempts. |
blocked | configuration_required | Invitation email isn't available in this environment. |
blocked | authorization_required | When it was time to send, the invitation was no longer pending, or the connection or its sponsor was no longer allowed to invite. |
outcome_unknown | reconciliation_required | FieldTask couldn't confirm whether the email was sent. It may have been. |
Delivery problems never remove the invitation. Once the invitation is accepted, more follow-up work for the new membership can appear on the same operation.
Operation results are kept for seven days, the same as the invitation's lifetime. After that, GET /public/v1/operations/{id} returns 410 operation_result_expired. That doesn't mean the invitation wasn't created.
If the email doesn't arrive
- Read the operation and check the email target.
- If it's
pending, wait and read it again later. - If it's
succeededoroutcome_unknown, ask the recipient to check spam or junk folders, and check theemailon the invitation for typos. - If it's
failedwithinvalid_upstream_data, the address is probably wrong. Invite the correct address; that's a different recipient, so it isn't blocked by the first invitation.
Don't create another invitation for the same address straight away. While the first one is pending, a second one is rejected with 422 validation_failed, and the API can't resend. FieldTask also limits how many invitations one address can receive per day. If you need to start over for the same address, have an owner or admin cancel the pending invitation in the FieldTask app, or wait until it has expired, then create a new one.
Accepting an invitation
Acceptance happens in FieldTask, not through the API. The recipient:
- Opens the link in the email before
expires_at. - Signs in to FieldTask with an account whose verified email address matches the invitation. Someone without an account creates one and verifies the address first, then returns to the link.
- Accepts. They join as an ordinary member, never as an admin.
Each link works once: after the invitation is accepted it can't be used again. Acceptance is refused when:
- the invitation has expired, been cancelled or already been accepted;
- the signed-in account's verified addresses don't include the invited address;
- the person was previously removed from the organization. An API invitation doesn't restore a removed member;
- the connection that created the invitation has been revoked or has lost
members:readormembers:invite, or the invitation's sponsor is no longer the connection's sponsor or no longer an owner or admin; - the organization's plan doesn't allow another member.
If the invited address belongs to someone who is already an active member, accepting marks the invitation accepted and leaves their membership unchanged.
What the API can't do
- Resend an invitation email, or extend an invitation.
- Cancel or revoke an invitation. Owners and admins do that in the FieldTask app.
- Accept an invitation on someone's behalf.
- Invite admins, change a member's role, or remove members.
Idempotency and safe retries
Networks fail after a request is sent. The Idempotency-Key header lets you retry without creating a second invitation. See Idempotency and recovery for the general rules.
- Before sending, generate a new key for the invitation, such as a UUID, and save it with your own record.
- Send the request with that key.
- If you get no response, a timeout or a
5xx, send the same body with the same key again, with backoff.
| Situation | Result |
|---|---|
| New key | The invitation is created. Response 201, Idempotency-Replayed: false. |
| Same key, same body, within 7 days | The original response is returned again. No second invitation is created, no second email is sent, and it isn't counted against your allowances. Response 201, Idempotency-Replayed: true. |
| Same key, different body | 409 idempotency_key_reused. Nothing is created. |
| Same key while the first request is still running | 409 operation_in_progress with Retry-After. Retry with the same key and body. |
| Same key more than 7 days later | 409 idempotency_window_expired. A key can never be reused for a different invitation. |
Details that matter:
- Keys are 16–128 printable ASCII characters.
- "Same body" means the same JSON value. Property order and whitespace don't matter, but leaving out
roleis different from sending"role": "member", and a different letter case inemailis a different body. - A replay returns the original response, so
data.statusispendingeven if the invitation has since been accepted. Read the invitation for its current status. - Keys are scoped to the organization, the connection and the kind of request. Sending the same key through a different connection is treated as a new request.
- Replays are authorized again. If the connection has lost a permission, or its sponsor is no longer eligible, the replay is rejected rather than returned. If the invitation was cancelled in the app, a replay returns
410 resource_gone. - Without a key, the server generates one and returns it in the
Idempotency-Keyresponse header. That only helps if you receive the response, so always send your own key.
Limits
Invitations count against the organization's and the connection's daily create allowance, which resets at 00:00 UTC.
Invitations also have their own hourly and daily limits, per organization, per connection and per recipient address. Hourly windows start on the hour and daily windows at 00:00 UTC. FieldTask sets these limits.
When you hit a limit you get 429 with a Retry-After header in seconds: until the window resets for invitation limits, or until 00:00 UTC for the daily create allowance. Retry with the same key. Replaying an already-created invitation with its original key doesn't count again.
Recovering from failures
| What happened | What to do |
|---|---|
Timeout, connection error or 5xx | Retry the same body with the same key, with exponential backoff. |
409 operation_in_progress | Wait for Retry-After seconds and retry with the same key. |
429 | Wait for Retry-After seconds and retry with the same key. |
422 validation_failed and you aren't sure whether an earlier attempt worked | List invitations newest first and look for the address before trying anything else. A pending invitation for it blocks a new one. |
403 operation_forbidden | A current owner or admin re-saves the connection's permissions to become its sponsor, then retry with the same key. |
201 received | Store data.id and the Location and Operation-Location headers. Stop retrying. |
| Key older than 7 days, or the result expired | Don't create again yet. Look for the invitation with GET /public/v1/member-invitations?ordering=-created_at. |
Never switch to a new key just because a response was lost.
Errors
Errors use application/problem+json. The top-level code is the stable machine-readable value; error adds a readable message, a suggested fix and a documentation link:
{
"type": "about:blank",
"title": "Unprocessable Entity",
"status": 422,
"code": "validation_failed",
"detail": "The request contains invalid fields.",
"instance": "urn:uuid:99999999-9999-4999-8999-999999999993",
"request_id": "99999999-9999-4999-8999-999999999993",
"error": {
"code": "validation_failed",
"message": "The request contains invalid fields.",
"fix": "Check the documented request fields.",
"docs_url": "https://api.fieldtask.example/public/docs/"
}
}
error.code usually equals code. For a missing permission, code is insufficient_scope and error.code is missing_permission. Validation errors don't say which rule failed, and error responses never echo submitted values.
| Status | code | Meaning | What to do |
|---|---|---|---|
| 400 | invalid_query | The URL has a query string. Create requests accept none. | Send the request to /public/v1/member-invitations with no query parameters. |
| 400 | invalid_json | The body isn't one valid JSON value, repeats a property name, uses NaN/Infinity or nests deeper than 10 levels. | Send a single JSON object encoded as UTF-8. |
| 400 | invalid_operation_key | Idempotency-Key isn't 16–128 printable ASCII characters. | Use a UUID or another 16–128 character key without spaces at either end. |
| 401 | invalid_token | The bearer token is missing, malformed, expired, revoked or issued for a different resource (for example an MCP sign-in). | Send a current REST API token or REST OAuth access token as Authorization: Bearer …. |
| 403 | api_access_not_enabled | FieldTask hasn't enabled API access for this organization, or has disabled it. | An owner or admin requests access in Settings → API connections. |
| 403 | insufficient_scope | The connection lacks members:read or members:invite. error.code is missing_permission. | Add the permission named in error.message to the connection, then retry with the same key. |
| 403 | operation_forbidden | The connection has no eligible invitation sponsor, its access changed while the request was being processed, or a replay was refused because the connection can no longer read the result. | A current owner or admin re-saves the connection's permissions in Settings → API connections to become the sponsor. Then retry with the same key and body. |
| 404 | resource_not_found | Invitations or invitation creation aren't available in this environment, or the URL is wrong. | Check the API origin and path (no trailing slash). Contact FieldTask if you expected invitation creation to be available. |
| 405 | method_not_allowed | Invitation reads are enabled but invitation creation isn't. The Allow header lists the methods that are. | Contact FieldTask if you expected invitation creation to be available. |
| 409 | idempotency_key_reused | This key was already used with a different body. | Resend the original body with this key, or use a new key for a different invitation. |
| 409 | operation_in_progress | A request with the same key is still being processed. | Wait for Retry-After seconds, then retry with the same key and body. |
| 409 | idempotency_window_expired | This key was used more than seven days ago. It can't be replayed or reused. This doesn't mean the original request failed. | Look for the invitation with GET /public/v1/member-invitations?ordering=-created_at before creating anything new. |
| 410 | resource_gone | A replay found that the invitation created with this key no longer exists, for example because it was cancelled in the FieldTask app. | Don't create it again automatically. Check with an owner or admin whether it was cancelled on purpose. |
| 413 | payload_too_large | The body is larger than 256 KiB. | Send only email and, optionally, role. |
| 415 | unsupported_media_type | Content-Type isn't application/json, or the body is compressed. | Send an uncompressed body with Content-Type: application/json. |
| 422 | validation_failed | email is missing or invalid, role isn't member, the body has another field, the address already has a pending invitation, or the organization's plan doesn't allow more invitations. | Check the request against the field guide. If you may have invited this address already, list invitations before retrying. The response doesn't name the rule. |
| 429 | rate_limit_exceeded | Too many requests in the current minute, too many concurrent requests, or an hourly or daily invitation limit for the organization, connection or recipient address was reached. | Wait for Retry-After seconds, then retry with the same key. |
| 429 | daily_quota_exceeded | The organization or connection has used its daily create allowance. Allowances reset at 00:00 UTC. | Wait for Retry-After seconds, or check remaining allowance in Settings → API connections. |
| 503 | temporarily_unavailable | The service couldn't complete the request. The invitation may or may not have been created. | Retry with the same key and body, with exponential backoff. |
Every response, including errors, has an X-Request-ID header. Include it, with the time and the status code, when you contact support. Never send tokens or secrets.
Response headers
A successful create returns 201 Created with these headers:
| Header | Always present | Value |
|---|---|---|
Cache-Control | Yes | Always no-store |
Idempotency-Key | Yes | string |
Idempotency-Replayed | Yes | string |
Location | Yes | string |
Operation-Location | Yes | string |
X-Request-ID | Yes | uuid |
The server also returns the Idempotency-Key that was used, including one it generated.